def test_login_with_missing_reply_files(source_app, app_storage): """ Test that source can log in when replies are present in database but missing from storage. """ source, codename = utils.db_helper.init_source(app_storage) journalist, _ = utils.db_helper.init_journalist() replies = utils.db_helper.reply(app_storage, journalist, source, 1) assert len(replies) > 0 # Delete the reply file reply_file_path = Path( app_storage.path(source.filesystem_id, replies[0].filename)) reply_file_path.unlink() assert not reply_file_path.exists() with source_app.test_client() as app: resp = app.get(url_for("main.login")) assert resp.status_code == 200 text = resp.data.decode("utf-8") assert "Enter Codename" in text resp = app.post(url_for("main.login"), data=dict(codename=codename), follow_redirects=True) assert resp.status_code == 200 text = resp.data.decode("utf-8") assert "Submit Files" in text assert SessionManager.is_user_logged_in(db_session=db.session)
def generate() -> Union[str, werkzeug.Response]: if request.method == "POST": # Try to detect Tor2Web usage by looking to see if tor2web_check got mangled tor2web_check = request.form.get("tor2web_check") if tor2web_check is None: # Missing form field abort(403) elif tor2web_check != 'href="fake.onion"': return redirect(url_for("info.tor2web_warning")) if SessionManager.is_user_logged_in(db_session=db.session): flash_msg( "notification", None, gettext( "You were redirected because you are already logged in. " "If you want to create a new account, you should log out first." ), ) return redirect(url_for(".lookup")) codename = PassphraseGenerator.get_default().generate_passphrase( preferred_language=g.localeinfo.language ) # Generate a unique id for each browser tab and associate the codename with this id. # This will allow retrieval of the codename displayed in the tab from which the source has # clicked to proceed to /generate (ref. issue #4458) tab_id = urlsafe_b64encode(os.urandom(64)).decode() codenames = session.get("codenames", {}) codenames[tab_id] = codename session["codenames"] = fit_codenames_into_cookie(codenames) session["codenames_expire"] = datetime.now(timezone.utc) + timedelta( minutes=config.SESSION_EXPIRATION_MINUTES ) return render_template("generate.html", codename=codename, tab_id=tab_id)
def test_create_duplicate_codename_logged_in_not_in_session(source_app): with patch.object(source_app.logger, "error") as logger: with source_app.test_client() as app: resp = app.post(url_for("main.generate"), data=GENERATE_DATA) assert resp.status_code == 200 tab_id, codename = next(iter(session["codenames"].items())) # Create a source the first time resp = app.post(url_for("main.create"), data={"tab_id": tab_id}, follow_redirects=True) assert resp.status_code == 200 with source_app.test_client() as app: # Attempt to add the same source with app.session_transaction() as sess: sess["codenames"] = {tab_id: codename} sess["codenames_expire"] = datetime.utcnow() + timedelta( hours=1) resp = app.post(url_for("main.create"), data={"tab_id": tab_id}, follow_redirects=True) logger.assert_called_once() assert "Could not create a source" in logger.call_args[0][0] assert resp.status_code == 200 assert not SessionManager.is_user_logged_in(db_session=db.session)
def test_login_and_logout(source_app): with source_app.test_client() as app: resp = app.get(url_for("main.login")) assert resp.status_code == 200 text = resp.data.decode("utf-8") assert "Enter Codename" in text codename = new_codename(app, session) resp = app.post(url_for("main.login"), data=dict(codename=codename), follow_redirects=True) assert resp.status_code == 200 text = resp.data.decode("utf-8") assert "Submit Files" in text assert SessionManager.is_user_logged_in(db_session=db.session) with source_app.test_client() as app: resp = app.post(url_for("main.login"), data=dict(codename="invalid"), follow_redirects=True) assert resp.status_code == 200 text = resp.data.decode("utf-8") assert "Sorry, that is not a recognized codename." in text assert not SessionManager.is_user_logged_in(db_session=db.session) with source_app.test_client() as app: resp = app.post(url_for("main.login"), data=dict(codename=codename), follow_redirects=True) assert resp.status_code == 200 assert SessionManager.is_user_logged_in(db_session=db.session) resp = app.post(url_for("main.login"), data=dict(codename=codename), follow_redirects=True) assert resp.status_code == 200 assert SessionManager.is_user_logged_in(db_session=db.session) resp = app.get(url_for("main.logout"), follow_redirects=True) assert not SessionManager.is_user_logged_in(db_session=db.session) text = resp.data.decode("utf-8") # This is part of the logout page message instructing users # to click the 'New Identity' icon assert "This will clear your Tor Browser activity data" in text
def test_source_is_deleted_while_logged_in(source_app): """If a source is deleted by a journalist when they are logged in, a NoResultFound will occur. The source should be redirected to the index when this happens, and a warning logged.""" with source_app.test_client() as app: codename = new_codename(app, session) app.post("login", data=dict(codename=codename), follow_redirects=True) # Now that the source is logged in, the journalist deletes the source source_user = SessionManager.get_logged_in_user(db_session=db.session) delete_collection(source_user.filesystem_id) # Source attempts to continue to navigate resp = app.get(url_for("main.lookup"), follow_redirects=True) assert resp.status_code == 200 assert not SessionManager.is_user_logged_in(db_session=db.session) text = resp.data.decode("utf-8") assert "First submission" in text assert not SessionManager.is_user_logged_in(db_session=db.session)
def login_test(app, codename): resp = app.get(url_for("main.login")) assert resp.status_code == 200 text = resp.data.decode("utf-8") assert "Enter Codename" in text resp = app.post(url_for("main.login"), data=dict(codename=codename), follow_redirects=True) assert resp.status_code == 200 text = resp.data.decode("utf-8") assert "Submit Files" in text assert SessionManager.is_user_logged_in(db_session=db.session)
def test_create_new_source(source_app): with source_app.test_client() as app: resp = app.post(url_for("main.generate"), data=GENERATE_DATA) assert resp.status_code == 200 tab_id = next(iter(session["codenames"].keys())) resp = app.post(url_for("main.create"), data={"tab_id": tab_id}, follow_redirects=True) assert SessionManager.is_user_logged_in(db_session=db.session) # should be redirected to /lookup text = resp.data.decode("utf-8") assert "Submit Files" in text assert "codenames" not in session
def test_submit_codename_second_login(source_app): """ Test codename submissions *not* prevented on second session """ with source_app.test_client() as app: InstanceConfig.get_default().update_submission_prefs( allow_uploads=True, min_length=0, reject_codenames=True) codename = new_codename(app, session) resp = app.post( url_for("main.submit"), data=dict(msg=codename, fh=(StringIO(""), "")), follow_redirects=True, ) assert resp.status_code == 200 text = resp.data.decode("utf-8") assert "Please do not submit your codename!" in text resp = app.get(url_for("main.logout"), follow_redirects=True) assert not SessionManager.is_user_logged_in(db_session=db.session) text = resp.data.decode("utf-8") assert "This will clear your Tor Browser activity data" in text resp = app.post(url_for("main.login"), data=dict(codename=codename), follow_redirects=True) assert resp.status_code == 200 assert SessionManager.is_user_logged_in(db_session=db.session) resp = app.post( url_for("main.submit"), data=dict(msg=codename, fh=(StringIO(""), "")), follow_redirects=True, ) assert resp.status_code == 200 text = resp.data.decode("utf-8") assert "Thank you for sending this information" in text
def test_log_user_in(self, source_app, app_storage): # Given a source user passphrase = PassphraseGenerator.get_default().generate_passphrase() source_user = create_source_user( db_session=db.session, source_passphrase=passphrase, source_app_storage=app_storage, ) with source_app.test_request_context(): # When they log in, it succeeds SessionManager.log_user_in(db_session=db.session, supplied_passphrase=passphrase) # And the SessionManager returns them as the current user assert SessionManager.is_user_logged_in(db_session=db.session) logged_in_user = SessionManager.get_logged_in_user(db_session=db.session) assert logged_in_user.db_record_id == source_user.db_record_id
def create() -> werkzeug.Response: if SessionManager.is_user_logged_in(db_session=db.session): flash_msg( "notification", None, gettext( "You are already logged in. Please verify your codename as it " "may differ from the one displayed on the previous page." ), ) else: # Ensure the codenames have not expired date_codenames_expire = session.get("codenames_expire") if not date_codenames_expire or datetime.now(timezone.utc) >= date_codenames_expire: return clear_session_and_redirect_to_logged_out_page(flask_session=session) tab_id = request.form["tab_id"] codename = session["codenames"][tab_id] del session["codenames"] try: current_app.logger.info("Creating new source user...") create_source_user( db_session=db.session, source_passphrase=codename, source_app_storage=Storage.get_default(), ) except (SourcePassphraseCollisionError, SourceDesignationCollisionError) as e: current_app.logger.error("Could not create a source: {}".format(e)) flash_msg( "error", None, gettext( "There was a temporary problem creating your account. Please try again." ), ) return redirect(url_for(".index")) # All done - source user was successfully created current_app.logger.info("New source user created") session["new_user_codename"] = codename SessionManager.log_user_in( db_session=db.session, supplied_passphrase=DicewarePassphrase(codename) ) return redirect(url_for(".lookup"))
def logout() -> Union[str, werkzeug.Response]: """ If a user is logged in, show them a logout page that prompts them to click the New Identity button in Tor Browser to complete their session. Otherwise redirect to the main Source Interface page. """ if SessionManager.is_user_logged_in(db_session=db.session): SessionManager.log_user_out() # Clear the session after we render the message so it's localized # If a user specified a locale, save it and restore it session.clear() session["locale"] = g.localeinfo.id return render_template("logout.html") else: return redirect(url_for(".index"))
def test_log_user_out(self, source_app, app_storage): # Given a source user passphrase = PassphraseGenerator.get_default().generate_passphrase() create_source_user( db_session=db.session, source_passphrase=passphrase, source_app_storage=app_storage, ) with source_app.test_request_context(): # Who previously logged in SessionManager.log_user_in(db_session=db.session, supplied_passphrase=passphrase) # When they log out, it succeeds SessionManager.log_user_out() # And the SessionManager no longer returns a current user assert not SessionManager.is_user_logged_in(db_session=db.session) with pytest.raises(UserNotLoggedIn): SessionManager.get_logged_in_user(db_session=db.session)
def test_normalize_timestamps(source_app, app_storage): """ Check function of source_app.utils.normalize_timestamps. All submissions for a source should have the same timestamp. Any existing submissions' files that did not exist at the time of a new submission should not be created by normalize_timestamps. """ with source_app.test_client() as app: # create a source source, codename = utils.db_helper.init_source(app_storage) # create one submission first_submission = submit(app_storage, source, 1)[0] # delete the submission's file from the store first_submission_path = Path( app_storage.path(source.filesystem_id, first_submission.filename)) first_submission_path.unlink() assert not first_submission_path.exists() # log in as the source resp = app.post(url_for("main.login"), data=dict(codename=codename), follow_redirects=True) assert resp.status_code == 200 text = resp.data.decode("utf-8") assert "Submit Files" in text assert SessionManager.is_user_logged_in(db_session=db.session) # submit another message resp = _dummy_submission(app) assert resp.status_code == 200 text = resp.data.decode("utf-8") assert "Thanks! We received your message" in text # sleep to ensure timestamps would differ time.sleep(1) # submit another message resp = _dummy_submission(app) assert resp.status_code == 200 text = resp.data.decode("utf-8") assert "Thanks! We received your message" in text # only two of the source's three submissions should have files in the store assert 3 == len(source.submissions) submission_paths = [ Path(app_storage.path(source.filesystem_id, s.filename)) for s in source.submissions ] extant_paths = [p for p in submission_paths if p.exists()] assert 2 == len(extant_paths) # verify that the deleted file has not been recreated assert not first_submission_path.exists() assert first_submission_path not in extant_paths # and the timestamps of all existing files should match exactly assert extant_paths[0].stat().st_atime_ns == extant_paths[1].stat( ).st_atime_ns assert extant_paths[0].stat().st_ctime_ns == extant_paths[1].stat( ).st_ctime_ns assert extant_paths[0].stat().st_mtime_ns == extant_paths[1].stat( ).st_mtime_ns