コード例 #1
0
    def test_generate_passphrase_with_specific_language(self):
        # Given a generator that supports two languages
        generator = PassphraseGenerator(language_to_words={
            "en": ["boat"],
            "fr": ["bateau"]
        })
        assert generator.available_languages == {"en", "fr"}

        # When using it to create a passphrase for one of the two languages
        # It succeeds
        passphrase = generator.generate_passphrase(preferred_language="fr")

        # And the passphrase is in the chosen language
        assert "bateau" in passphrase
        assert "boat" not in passphrase
コード例 #2
0
ファイル: admin.py プロジェクト: freedomofpress/securedrop
    def edit_user(user_id: int) -> Union[str, werkzeug.Response]:
        user = Journalist.query.get(user_id)

        if request.method == "POST":
            if request.form.get("username", None):
                new_username = request.form["username"]

                try:
                    Journalist.check_username_acceptable(new_username)
                except InvalidUsernameException as e:
                    flash(
                        gettext("Invalid username: {message}").format(
                            message=e), "error")
                    return redirect(url_for("admin.edit_user",
                                            user_id=user_id))

                if new_username == user.username:
                    pass
                elif Journalist.query.filter_by(
                        username=new_username).one_or_none():
                    flash(
                        gettext('Username "{username}" already taken.').format(
                            username=new_username),
                        "error",
                    )
                    return redirect(url_for("admin.edit_user",
                                            user_id=user_id))
                else:
                    user.username = new_username

            try:
                first_name = request.form["first_name"]
                Journalist.check_name_acceptable(first_name)
                user.first_name = first_name
            except FirstOrLastNameError as e:
                # Translators: Here, "{message}" explains the problem with the name.
                flash(
                    gettext("Name not updated: {message}").format(message=e),
                    "error")
                return redirect(url_for("admin.edit_user", user_id=user_id))

            try:
                last_name = request.form["last_name"]
                Journalist.check_name_acceptable(last_name)
                user.last_name = last_name
            except FirstOrLastNameError as e:
                flash(
                    gettext("Name not updated: {message}").format(message=e),
                    "error")
                return redirect(url_for("admin.edit_user", user_id=user_id))

            user.is_admin = bool(request.form.get("is_admin"))

            commit_account_changes(user)

        password = PassphraseGenerator.get_default().generate_passphrase(
            preferred_language=g.localeinfo.language)
        return render_template("edit_account.html",
                               user=user,
                               password=password)
コード例 #3
0
def test_reply_keypair_creation_and_expiration_dates(source_app):
    with source_app.app_context():
        codename = PassphraseGenerator.get_default().generate_passphrase()
        filesystem_id = source_app.crypto_util.hash_codename(codename)
        journalist_filename = source_app.crypto_util.display_id()
        source = models.Source(filesystem_id, journalist_filename)
        db.session.add(source)
        db.session.commit()
        source_app.crypto_util.genkeypair(source.filesystem_id, codename)

        # crypto_util.get_fingerprint only returns the fingerprint of the key. We need
        # the full output of gpg.list_keys() to check the creation and
        # expire dates.
        #
        # TODO: it might be generally useful to refactor crypto_util.get_fingerprint so
        # it always returns the entire key dictionary instead of just the
        # fingerprint (which is always easily extracted from the entire key
        # dictionary).
        new_key_fingerprint = source_app.crypto_util.get_fingerprint(
            filesystem_id)
        new_key = [
            key for key in source_app.crypto_util.gpg.list_keys()
            if new_key_fingerprint == key['fingerprint']
        ][0]

        # All keys should share the same creation date to avoid leaking
        # information about when sources first created accounts.
        creation_date = parse_gpg_date_string(new_key['date'])
        assert (creation_date.date() == CryptoUtil.DEFAULT_KEY_CREATION_DATE)

        # Reply keypairs should not expire
        expire_date = new_key['expires']
        assert expire_date == ''
コード例 #4
0
ファイル: main.py プロジェクト: freedomofpress/securedrop
    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)
コード例 #5
0
    def test_encrypt_and_decrypt_journalist_reply(self, source_app,
                                                  test_source, tmp_path,
                                                  app_storage):
        # Given a source user with a key pair in the default encryption manager
        source_user1 = test_source["source_user"]
        encryption_mgr = EncryptionManager.get_default()

        # And another source with a key pair in the default encryption manager
        with source_app.app_context():
            source_user2 = create_source_user(
                db_session=db.session,
                source_passphrase=PassphraseGenerator.get_default().
                generate_passphrase(),
                source_app_storage=app_storage,
            )
        encryption_mgr.generate_source_key_pair(source_user2)

        # When the journalist tries to encrypt a reply to source1
        # It succeeds
        journalist_reply = "s3cr3t message"
        encrypted_reply_path = tmp_path / "reply.gpg"
        encryption_mgr.encrypt_journalist_reply(
            for_source_with_filesystem_id=source_user1.filesystem_id,
            reply_in=journalist_reply,
            encrypted_reply_path_out=encrypted_reply_path,
        )

        # And the output file contains the encrypted data
        encrypted_reply = encrypted_reply_path.read_bytes()
        assert encrypted_reply

        # And source1 is able to decrypt the reply
        decrypted_reply = encryption_mgr.decrypt_journalist_reply(
            for_source_user=source_user1, ciphertext_in=encrypted_reply)
        assert decrypted_reply
        assert decrypted_reply == journalist_reply

        # And source2 is NOT able to decrypt the reply
        with pytest.raises(GpgDecryptError):
            encryption_mgr.decrypt_journalist_reply(
                for_source_user=source_user2, ciphertext_in=encrypted_reply)

        # Amd the reply can't be decrypted without providing the source1's gpg secret
        result = encryption_mgr._gpg.decrypt(
            # For GPG 2.1+, a non-null passphrase _must_ be passed to decrypt()
            encrypted_reply,
            passphrase="test 123",
        )
        assert not result.ok

        # And the journalist is able to decrypt their reply
        with import_journalist_private_key(encryption_mgr):
            decrypted_reply_for_journalist = encryption_mgr._gpg.decrypt(
                # For GPG 2.1+, a non-null passphrase _must_ be passed to decrypt()
                encrypted_reply,
                passphrase="test 123",
            ).data
        assert decrypted_reply_for_journalist.decode() == journalist_reply
コード例 #6
0
    def add_user() -> Union[str, werkzeug.Response]:
        form = NewUserForm()
        if form.validate_on_submit():
            form_valid = True
            username = request.form['username']
            first_name = request.form['first_name']
            last_name = request.form['last_name']
            password = request.form['password']
            is_admin = bool(request.form.get('is_admin'))

            try:
                otp_secret = None
                if request.form.get('is_hotp', False):
                    otp_secret = request.form.get('otp_secret', '')
                new_user = Journalist(username=username,
                                      password=password,
                                      first_name=first_name,
                                      last_name=last_name,
                                      is_admin=is_admin,
                                      otp_secret=otp_secret)
                db.session.add(new_user)
                db.session.commit()
            except PasswordError:
                flash(
                    gettext(
                        'There was an error with the autogenerated password. '
                        'User not created. Please try again.'), 'error')
                form_valid = False
            except InvalidUsernameException as e:
                form_valid = False
                flash('Invalid username: '******'Username "{user}" already taken.'.format(
                            user=username)), "error")
                else:
                    flash(
                        gettext("An error occurred saving this user"
                                " to the database."
                                " Please inform your admin."), "error")
                    current_app.logger.error("Adding user "
                                             "'{}' failed: {}".format(
                                                 username, e))

            if form_valid:
                return redirect(
                    url_for('admin.new_user_two_factor', uid=new_user.id))

        password = PassphraseGenerator.get_default().generate_passphrase(
            preferred_language=g.localeinfo.language)
        return render_template("admin_add_user.html",
                               password=password,
                               form=form)
コード例 #7
0
ファイル: conftest.py プロジェクト: freedomofpress/securedrop
def _create_source_and_submission(config_in_use: SecureDropConfig) -> Path:
    """Directly create a source and a submission within the app.

    Some tests for the journalist app require a submission to already be present, and this
    function is used to create the source user and submission when the journalist app starts.

    This implementation is much faster than using Selenium to navigate the source app in order
    to create a submission: it takes 0.2s to run, while the Selenium implementation takes 7s.
    """
    # This function will be called in a separate Process that runs the app
    # Hence the late imports
    from encryption import EncryptionManager
    from models import Submission
    from passphrases import PassphraseGenerator
    from source_user import create_source_user
    from store import Storage, add_checksum_for_file
    from tests.functional.db_session import get_database_session

    # Create a source
    passphrase = PassphraseGenerator.get_default().generate_passphrase()
    with get_database_session(
            database_uri=config_in_use.DATABASE_URI) as db_session:
        source_user = create_source_user(
            db_session=db_session,
            source_passphrase=passphrase,
            source_app_storage=Storage.get_default(),
        )
        source_db_record = source_user.get_db_record()
        EncryptionManager.get_default().generate_source_key_pair(source_user)

        # Create a file submission from this source
        source_db_record.interaction_count += 1
        app_storage = Storage.get_default()
        encrypted_file_name = app_storage.save_file_submission(
            filesystem_id=source_user.filesystem_id,
            count=source_db_record.interaction_count,
            journalist_filename=source_db_record.journalist_filename,
            filename="filename.txt",
            stream=BytesIO(b"File with S3cr3t content"),
        )
        submission = Submission(source_db_record, encrypted_file_name,
                                app_storage)
        db_session.add(submission)
        source_db_record.pending = False
        source_db_record.last_updated = datetime.now(timezone.utc)
        db_session.commit()

        submission_file_path = app_storage.path(source_user.filesystem_id,
                                                submission.filename)
        add_checksum_for_file(
            session=db_session,
            db_obj=submission,
            file_path=submission_file_path,
        )

        return Path(submission_file_path)
コード例 #8
0
def init_journalist(first_name=None, last_name=None, is_admin=False):
    """Initialize a journalist into the database. Return their
    :class:`Journalist` object and password string.

    :param bool is_admin: Whether the user is an admin.

    :returns: A 2-tuple. The first entry, an :obj:`Journalist`
              corresponding to the row just added to the database. The
              second, their password string.
    """
    username = PassphraseGenerator.get_default().generate_passphrase()
    user_pw = PassphraseGenerator.get_default().generate_passphrase()
    user = Journalist(username=username,
                      password=user_pw,
                      first_name=first_name,
                      last_name=last_name,
                      is_admin=is_admin)
    db.session.add(user)
    db.session.commit()
    return user, user_pw
コード例 #9
0
    def test_default_generator_passphrases_are_random(self):
        # Given the default generator for the Securedrop app
        generator = PassphraseGenerator.get_default()

        # When using it to generate two passphrases
        # It succeeds
        passphrase1 = generator.generate_passphrase()
        passphrase2 = generator.generate_passphrase()

        # And the two passphrases are different because they are randomly-generated
        assert passphrase1 != passphrase2
コード例 #10
0
    def test_generate_passphrase_with_specific_language_that_is_not_available(
            self):
        # Given a generator that supports two languages
        generator = PassphraseGenerator(
            language_to_words={
                "en": ["boat"],
                "fr": ["bateau"]
            },
            # With english as the fallback language
            fallback_language="en",
        )
        assert generator.available_languages == {"en", "fr"}

        # When using it to create a passphrase for another, non-supported language
        # It succeeds
        passphrase = generator.generate_passphrase(preferred_language="es")

        # And the passphrase is in the default/fallback language, english
        assert "boat" in passphrase
        assert "bateau" not in passphrase
コード例 #11
0
    def test_create_source_user(self, source_app, app_storage):
        # Given a passphrase
        passphrase = PassphraseGenerator.get_default().generate_passphrase()

        # When trying to create a new source user with this passphrase, it succeeds
        source_user = create_source_user(
            db_session=db.session,
            source_passphrase=passphrase,
            source_app_storage=app_storage,
        )
        assert source_user
        assert source_user.get_db_record()
コード例 #12
0
    def test_authenticate_source_user_wrong_passphrase(self, source_app, app_storage):
        # Given a source in the DB
        create_source_user(
            db_session=db.session,
            source_passphrase=PassphraseGenerator.get_default().generate_passphrase(),
            source_app_storage=app_storage,
        )

        # When a user tries to authenticate using a wrong passphrase, it fails
        wrong_passphrase = "rehydrate flaring study raven fence extenuate linguist"
        with pytest.raises(InvalidPassphraseError):
            authenticate_source_user(db_session=db.session, supplied_passphrase=wrong_passphrase)
コード例 #13
0
ファイル: utils.py プロジェクト: jingru97/securedrop
def generate_unique_codename(config: SDConfig) -> DicewarePassphrase:
    """Generate random codenames until we get an unused one"""
    while True:
        passphrase = PassphraseGenerator.get_default().generate_passphrase(
            preferred_language=g.localeinfo.language)
        # scrypt (slow)
        filesystem_id = current_app.crypto_util.hash_codename(passphrase)

        matching_sources = Source.query.filter(
            Source.filesystem_id == filesystem_id).all()
        if len(matching_sources) == 0:
            return passphrase
コード例 #14
0
def test_genkeypair(source_app):
    with source_app.app_context():
        codename = PassphraseGenerator.get_default().generate_passphrase()
        filesystem_id = source_app.crypto_util.hash_codename(codename)
        journalist_filename = source_app.crypto_util.display_id()
        source = models.Source(filesystem_id, journalist_filename)
        db.session.add(source)
        db.session.commit()
        source_app.crypto_util.genkeypair(source.filesystem_id, codename)

        assert source_app.crypto_util.get_fingerprint(
            filesystem_id) is not None
コード例 #15
0
    def test_default_generator(self):
        # Given the default generator for the Securedrop app
        generator = PassphraseGenerator.get_default()
        assert generator.available_languages == {"en", "fr"}

        # When using it to generate a passphrase
        # It succeeds
        passphrase = generator.generate_passphrase()

        # And a reasonably-secure passphrase was generated
        assert passphrase
        assert len(passphrase) >= 20
        assert len(passphrase.split(" ")) >= 7
コード例 #16
0
    def test_create_source_user_designation_collision(self, source_app, app_storage):
        # Given a source in the DB
        existing_source = create_source_user(
            db_session=db.session,
            source_passphrase=PassphraseGenerator.get_default().generate_passphrase(),
            source_app_storage=app_storage,
        )
        existing_designation = existing_source.get_db_record().journalist_designation

        # And the next generated journalist designation will be identical to this source's
        with mock.patch.object(
            source_user._DesignationGenerator,
            "generate_journalist_designation",
            return_value=existing_designation,
        ):
            # When trying to create another source, it fails, because the designation is the same
            with pytest.raises(SourceDesignationCollisionError):
                create_source_user(
                    db_session=db.session,
                    source_passphrase=PassphraseGenerator.get_default().generate_passphrase(),
                    source_app_storage=app_storage,
                )
コード例 #17
0
    def edit_user(user_id: int) -> Union[str, werkzeug.Response]:
        user = Journalist.query.get(user_id)

        if request.method == 'POST':
            if request.form.get('username', None):
                new_username = request.form['username']

                try:
                    Journalist.check_username_acceptable(new_username)
                except InvalidUsernameException as e:
                    flash('Invalid username: '******'error')
                    return redirect(url_for("admin.edit_user",
                                            user_id=user_id))

                if new_username == user.username:
                    pass
                elif Journalist.query.filter_by(
                        username=new_username).one_or_none():
                    flash(
                        gettext('Username "{user}" already taken.').format(
                            user=new_username), "error")
                    return redirect(url_for("admin.edit_user",
                                            user_id=user_id))
                else:
                    user.username = new_username

            try:
                first_name = request.form['first_name']
                Journalist.check_name_acceptable(first_name)
                user.first_name = first_name
            except FirstOrLastNameError as e:
                flash(gettext('Name not updated: {}'.format(e)), "error")
                return redirect(url_for("admin.edit_user", user_id=user_id))

            try:
                last_name = request.form['last_name']
                Journalist.check_name_acceptable(last_name)
                user.last_name = last_name
            except FirstOrLastNameError as e:
                flash(gettext('Name not updated: {}'.format(e)), "error")
                return redirect(url_for("admin.edit_user", user_id=user_id))

            user.is_admin = bool(request.form.get('is_admin'))

            commit_account_changes(user)

        password = PassphraseGenerator.get_default().generate_passphrase(
            preferred_language=g.localeinfo.language)
        return render_template("edit_account.html",
                               user=user,
                               password=password)
コード例 #18
0
    def test_create_source_user_passphrase_collision(self, source_app, app_storage):
        # Given a source in the DB
        passphrase = PassphraseGenerator.get_default().generate_passphrase()
        create_source_user(
            db_session=db.session,
            source_passphrase=passphrase,
            source_app_storage=app_storage,
        )

        # When trying to create another with the same passphrase, it fails
        with pytest.raises(SourcePassphraseCollisionError):
            create_source_user(
                db_session=db.session,
                source_passphrase=passphrase,
                source_app_storage=app_storage,
            )
コード例 #19
0
def init_source(storage):
    """Initialize a source: create their database record, the
    filesystem directory that stores their submissions & replies,
    and their GPG key encrypted with their codename. Return a source
    object and their codename string.

    :returns: A 2-tuple. The first entry, the :class:`Source`
    initialized. The second, their codename string.
    """
    passphrase = PassphraseGenerator.get_default().generate_passphrase()
    source_user = create_source_user(
        db_session=db.session,
        source_passphrase=passphrase,
        source_app_storage=storage,
    )
    EncryptionManager.get_default().generate_source_key_pair(source_user)
    return source_user.get_db_record(), passphrase
コード例 #20
0
def test_add_checksum_for_file(config, app_storage, db_model):
    """
    Check that when we execute the `add_checksum_for_file` function, the database object is
    correctly updated with the actual hash of the file.

    We have to create our own app in order to have more control over the SQLAlchemy sessions. The
    fixture pushes a single app context that forces us to work within a single transaction.
    """
    app = create_app(config)

    test_storage = app_storage

    with app.app_context():
        db.create_all()
        source_user = create_source_user(
            db_session=db.session,
            source_passphrase=PassphraseGenerator.get_default().
            generate_passphrase(),
            source_app_storage=test_storage,
        )
        source = source_user.get_db_record()
        target_file_path = test_storage.path(source.filesystem_id,
                                             "1-foo-msg.gpg")
        test_message = b"hash me!"
        expected_hash = "f1df4a6d8659471333f7f6470d593e0911b4d487856d88c83d2d187afa195927"

        with open(target_file_path, "wb") as f:
            f.write(test_message)

        if db_model == Submission:
            db_obj = Submission(source, target_file_path, app_storage)
        else:
            journalist, _ = utils.db_helper.init_journalist()
            db_obj = Reply(journalist, source, target_file_path, app_storage)

        db.session.add(db_obj)
        db.session.commit()
        db_obj_id = db_obj.id

    queued_add_checksum_for_file(db_model, db_obj_id, target_file_path,
                                 app.config["SQLALCHEMY_DATABASE_URI"])

    with app.app_context():
        # requery to get a new object
        db_obj = db_model.query.filter_by(id=db_obj_id).one()
        assert db_obj.checksum == "sha256:" + expected_hash
コード例 #21
0
    def test_authenticate_source_user(self, source_app, app_storage):
        # Given a source in the DB
        passphrase = PassphraseGenerator.get_default().generate_passphrase()
        source_user = create_source_user(
            db_session=db.session,
            source_passphrase=passphrase,
            source_app_storage=app_storage,
        )

        # When they try to authenticate using their passphrase
        authenticated_user = authenticate_source_user(
            db_session=db.session, supplied_passphrase=passphrase
        )

        # It succeeds and the user is mapped to the right source in the DB
        assert authenticated_user
        assert authenticated_user.db_record_id == source_user.db_record_id
コード例 #22
0
    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
コード例 #23
0
def add_source() -> Tuple[Source, str]:
    """
    Adds a single source.
    """
    codename = PassphraseGenerator.get_default().generate_passphrase()
    source_user = create_source_user(
        db_session=db.session,
        source_passphrase=codename,
        source_app_storage=Storage.get_default(),
    )
    source = source_user.get_db_record()
    source.pending = False
    db.session.commit()

    # Generate source key
    EncryptionManager.get_default().generate_source_key_pair(source_user)

    return source, codename
コード例 #24
0
    def test_generate_source_key_pair(self,
                                      setup_journalist_key_and_gpg_folder,
                                      source_app, app_storage):
        # Given a source user
        with source_app.app_context():
            source_user = create_source_user(
                db_session=db.session,
                source_passphrase=PassphraseGenerator.get_default().
                generate_passphrase(),
                source_app_storage=app_storage,
            )

        # And an encryption manager
        journalist_key_fingerprint, gpg_key_dir = setup_journalist_key_and_gpg_folder
        encryption_mgr = EncryptionManager(
            gpg_key_dir=gpg_key_dir,
            journalist_key_fingerprint=journalist_key_fingerprint)

        # When using the encryption manager to generate a key pair for this source user
        # It succeeds
        encryption_mgr.generate_source_key_pair(source_user)

        # And the newly-created key's fingerprint was added to Redis
        fingerprint_in_redis = encryption_mgr._redis.hget(
            encryption_mgr.REDIS_FINGERPRINT_HASH, source_user.filesystem_id)
        assert fingerprint_in_redis
        source_key_fingerprint = encryption_mgr.get_source_key_fingerprint(
            source_user.filesystem_id)
        assert fingerprint_in_redis == source_key_fingerprint

        # And the user's newly-generated public key can be retrieved
        assert encryption_mgr.get_source_public_key(source_user.filesystem_id)

        # And the key has a hardcoded creation date to avoid leaking information about when sources
        # first created their account
        source_key_details = encryption_mgr._get_source_key_details(
            source_user.filesystem_id)
        assert source_key_details
        creation_date = _parse_gpg_date_string(source_key_details["date"])
        assert creation_date.date(
        ) == EncryptionManager.DEFAULT_KEY_CREATION_DATE

        # And the user's key does not expire
        assert source_key_details["expires"] == ""
コード例 #25
0
ファイル: loaddata.py プロジェクト: jingru97/securedrop
def add_source() -> Tuple[Source, str]:
    """
    Adds a single source.
    """
    codename = PassphraseGenerator.get_default().generate_passphrase()
    filesystem_id = current_app.crypto_util.hash_codename(codename)
    journalist_designation = current_app.crypto_util.display_id()
    source = Source(filesystem_id, journalist_designation)
    source.pending = False
    db.session.add(source)
    db.session.commit()

    # Create source directory in store
    os.mkdir(current_app.storage.path(source.filesystem_id))

    # Generate source key
    current_app.crypto_util.genkeypair(source.filesystem_id, codename)

    return source, codename
コード例 #26
0
ファイル: conftest.py プロジェクト: freedomofpress/securedrop
def test_source(journalist_app: Flask, app_storage: Storage) -> Dict[str, Any]:
    with journalist_app.app_context():
        passphrase = PassphraseGenerator.get_default().generate_passphrase()
        source_user = create_source_user(
            db_session=db.session,
            source_passphrase=passphrase,
            source_app_storage=app_storage,
        )
        EncryptionManager.get_default().generate_source_key_pair(source_user)
        source = source_user.get_db_record()
        return {
            "source_user": source_user,
            # TODO(AD): Eventually the next keys could be removed as they are in source_user
            "source": source,
            "codename": passphrase,
            "filesystem_id": source_user.filesystem_id,
            "uuid": source.uuid,
            "id": source.id,
        }
コード例 #27
0
def init_source_without_keypair():
    """Initialize a source: create their database record and the
    filesystem directory that stores their submissions & replies.
    Return a source object and their codename string.

    :returns: A 2-tuple. The first entry, the :class:`Source`
    initialized. The second, their codename string.
    """
    # Create source identity and database record
    codename = PassphraseGenerator.get_default().generate_passphrase()
    filesystem_id = current_app.crypto_util.hash_codename(codename)
    journalist_filename = current_app.crypto_util.display_id()
    source = Source(filesystem_id, journalist_filename)
    db.session.add(source)
    db.session.commit()
    # Create the directory to store their submissions and replies
    os.mkdir(current_app.storage.path(source.filesystem_id))

    return source, codename
コード例 #28
0
    def test_get_logged_in_user_but_user_deleted(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():
            # Who previously logged in
            SessionManager.log_user_in(db_session=db.session, supplied_passphrase=passphrase)
            # But since then their account was deleted
            source_in_db = source_user.get_db_record()
            source_in_db.deleted_at = datetime.utcnow()
            db.session.commit()

            # When querying the current user from the SessionManager, it fails with the right error
            with pytest.raises(UserHasBeenDeleted):
                SessionManager.get_logged_in_user(db_session=db.session)
コード例 #29
0
    def get_deleted(cls) -> "Journalist":
        """Get a system user that represents deleted journalists for referential integrity

        Callers must commit the session themselves
        """
        deleted = Journalist.query.filter_by(username="******").one_or_none()
        if deleted is None:
            # Lazily create
            deleted = cls(
                # Use a placeholder username to bypass validation that would reject
                # "deleted" as unusable
                username="******",
                # We store a randomly generated passphrase for this account that is
                # never revealed to anyone.
                password=PassphraseGenerator.get_default().generate_passphrase(
                ),
            )
            deleted.username = "******"
            db.session.add(deleted)
        return deleted
コード例 #30
0
    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)