Beispiel #1
0
    def test_source_is_deleted_while_logged_in(self, logger):
        """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 self.client as client:
            codename = new_codename(client, session)
            resp = client.post('login',
                               data=dict(codename=codename),
                               follow_redirects=True)

            # Now the journalist deletes the source
            filesystem_id = crypto_util.hash_codename(codename)
            crypto_util.delete_reply_keypair(filesystem_id)
            source = Source.query.filter_by(filesystem_id=filesystem_id).one()
            db_session.delete(source)
            db_session.commit()

            # Source attempts to continue to navigate
            resp = client.post('/lookup', follow_redirects=True)
            self.assertEqual(resp.status_code, 200)
            self.assertIn('Submit documents for the first time', resp.data)
            self.assertNotIn('logged_in', session.keys())
            self.assertNotIn('codename', session.keys())

        logger.assert_called_once_with(
            "Found no Sources when one was expected: "
            "No row was found for one()")
Beispiel #2
0
    def _can_decrypt_with_key(self, msg, key_fpr, passphrase=None):
        """
        Test that the given GPG message can be decrypted with the given key
        (identified by its fingerprint).
        """
        # GPG does not provide a way to specify which key to use to decrypt a
        # message. Since the default keyring that we use has both the
        # `config.JOURNALIST_KEY` and all of the reply keypairs, there's no way
        # to use it to test whether a message is decryptable with a specific
        # key.
        gpg_tmp_dir = tempfile.mkdtemp()
        gpg = gnupg.GPG(homedir=gpg_tmp_dir)

        # Export the key of interest from the application's keyring
        pubkey = self.gpg.export_keys(key_fpr)
        seckey = self.gpg.export_keys(key_fpr, secret=True)
        # Import it into our isolated temporary GPG directory
        for key in (pubkey, seckey):
            gpg.import_keys(key)

        # Attempt decryption with the given key
        if passphrase:
            passphrase = crypto_util.hash_codename(
                passphrase, salt=crypto_util.SCRYPT_GPG_PEPPER)
        decrypted_data = gpg.decrypt(msg, passphrase=passphrase)
        self.assertTrue(
            decrypted_data.ok,
            "Could not decrypt msg with key, gpg says: {}".format(
                decrypted_data.stderr))

        # We have to clean up the temporary GPG dir
        shutil.rmtree(gpg_tmp_dir)
    def wait_for_source_key(self, source_name):
        filesystem_id = crypto_util.hash_codename(source_name)

        def key_available(filesystem_id):
            assert crypto_util.getkey(filesystem_id)
        self.wait_for(
            lambda: key_available(filesystem_id), timeout=60)
    def _can_decrypt_with_key(self, msg, key_fpr, passphrase=None):
        """
        Test that the given GPG message can be decrypted with the given key
        (identified by its fingerprint).
        """
        # GPG does not provide a way to specify which key to use to decrypt a
        # message. Since the default keyring that we use has both the
        # `config.JOURNALIST_KEY` and all of the reply keypairs, there's no way
        # to use it to test whether a message is decryptable with a specific
        # key.
        gpg_tmp_dir = tempfile.mkdtemp()
        gpg = gnupg.GPG(homedir=gpg_tmp_dir)

        # Export the key of interest from the application's keyring
        pubkey = self.gpg.export_keys(key_fpr)
        seckey = self.gpg.export_keys(key_fpr, secret=True)
        # Import it into our isolated temporary GPG directory
        for key in (pubkey, seckey):
            gpg.import_keys(key)

        # Attempt decryption with the given key
        if passphrase:
            passphrase = crypto_util.hash_codename(
                passphrase,
                salt=crypto_util.SCRYPT_GPG_PEPPER)
        decrypted_data = gpg.decrypt(msg, passphrase=passphrase)
        self.assertTrue(
            decrypted_data.ok,
            "Could not decrypt msg with key, gpg says: {}".format(
                decrypted_data.stderr))

        # We have to clean up the temporary GPG dir
        shutil.rmtree(gpg_tmp_dir)
    def test_source_is_deleted_while_logged_in(self, logger):
        """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 self.client as client:
            codename = new_codename(client, session)
            resp = client.post('login', data=dict(codename=codename),
                               follow_redirects=True)

            # Now the journalist deletes the source
            filesystem_id = crypto_util.hash_codename(codename)
            crypto_util.delete_reply_keypair(filesystem_id)
            source = Source.query.filter_by(filesystem_id=filesystem_id).one()
            db_session.delete(source)
            db_session.commit()

            # Source attempts to continue to navigate
            resp = client.post('/lookup', follow_redirects=True)
            self.assertEqual(resp.status_code, 200)
            self.assertIn('Submit documents for the first time', resp.data)
            self.assertNotIn('logged_in', session.keys())
            self.assertNotIn('codename', session.keys())

        logger.assert_called_once_with(
            "Found no Sources when one was expected: "
            "No row was found for one()")
Beispiel #6
0
    def wait_for_source_key(self, source_name):
        filesystem_id = crypto_util.hash_codename(source_name)

        def key_available(filesystem_id):
            assert crypto_util.getkey(filesystem_id)

        self.wait_for(lambda: key_available(filesystem_id), timeout=60)
Beispiel #7
0
def generate_unique_codename(num_words):
    """Generate random codenames until we get an unused one"""
    while True:
        codename = crypto_util.genrandomid(num_words)
        sid = crypto_util.hash_codename(codename)  # scrypt (slow)
        matching_sources = Source.query.filter(Source.filesystem_id == sid).all()
        if len(matching_sources) == 0:
            return codename
Beispiel #8
0
def create():
    sid = crypto_util.hash_codename(session['codename'])
    if os.path.exists(store.path(sid)):
        # if this happens, we're not using very secure crypto
        app.logger.warning("Got a duplicate ID '%s'" % sid)
    else:
        os.mkdir(store.path(sid))
    session['logged_in'] = True
    session['flagged'] = False
    return redirect(url_for('lookup'))
    def test_genkeypair(self):
        codename = crypto_util.genrandomid()
        filesystem_id = crypto_util.hash_codename(codename)
        journalist_filename = crypto_util.display_id()
        source = db.Source(filesystem_id, journalist_filename)
        db.db_session.add(source)
        db.db_session.commit()
        crypto_util.genkeypair(source.filesystem_id, codename)

        self.assertIsNotNone(crypto_util.getkey(filesystem_id))
Beispiel #10
0
def valid_codename(codename):
    try:
        filesystem_id = crypto_util.hash_codename(codename)
    except crypto_util.CryptoException as e:
        app.logger.warning(
            "Could not compute filesystem ID for codename '{}': {}".format(
                codename, e))
        abort(500)
    source = Source.query.filter_by(filesystem_id=filesystem_id).first()
    return source is not None
Beispiel #11
0
def create():
    sid = crypto_util.hash_codename(session['codename'])
    if os.path.exists(store.path(sid)):
        # if this happens, we're not using very secure crypto
        log.warning("Got a duplicate ID '%s'" % sid)
    else:
        os.mkdir(store.path(sid))
    session['logged_in'] = True
    session['flagged'] = False
    return redirect(url_for('lookup'))
Beispiel #12
0
def setup_g():
    """Store commonly used values in Flask's special g object"""
    # ignore_static here because `crypto_util.hash_codename` is scrypt (very
    # time consuming), and we don't need to waste time running if we're just
    # serving a static resource that won't need to access these common values.
    if logged_in():
        # We use session.get (which defaults to None if 'flagged' is not in the
        # session) to avoid a KeyError on the redirect from login/ to lookup/
        g.flagged = session.get('flagged')
        g.codename = session['codename']
        g.sid = crypto_util.hash_codename(g.codename)
        g.loc = store.path(g.sid)
Beispiel #13
0
def setup_g():
    """Store commonly used values in Flask's special g object"""
    # ignore_static here because `crypto_util.hash_codename` is scrypt (very
    # time consuming), and we don't need to waste time running if we're just
    # serving a static resource that won't need to access these common values.
    if logged_in():
        # We use session.get (which defaults to None if 'flagged' is not in the
        # session) to avoid a KeyError on the redirect from login/ to lookup/
        g.flagged = session.get('flagged')
        g.codename = session['codename']
        g.sid = crypto_util.hash_codename(g.codename)
        g.loc = store.path(g.sid)
Beispiel #14
0
def create():
    sid = crypto_util.hash_codename(session['codename'])

    source = Source(sid, crypto_util.display_id())
    db_session.add(source)
    try:
        db_session.commit()
    except IntegrityError as e:
        app.logger.error("Attempt to create a source with duplicate codename: %s" % (e,))
    else:
        os.mkdir(store.path(sid))

    session['logged_in'] = True
    return redirect(url_for('lookup'))
Beispiel #15
0
def create():
    sid = crypto_util.hash_codename(session['codename'])

    source = Source(sid, crypto_util.display_id())
    db_session.add(source)
    db_session.commit()

    if os.path.exists(store.path(sid)):
        # if this happens, we're not using very secure crypto
        log.warning("Got a duplicate ID '%s'" % sid)
    else:
        os.mkdir(store.path(sid))

    session['logged_in'] = True
    return redirect(url_for('lookup'))
Beispiel #16
0
def setup_g():
    """Store commonly used values in Flask's special g object"""
    # ignore_static here because `crypto_util.hash_codename` is scrypt (very
    # time consuming), and we don't need to waste time running if we're just
    # serving a static resource that won't need to access these common values.
    if logged_in():
        g.codename = session['codename']
        g.sid = crypto_util.hash_codename(g.codename)
        try:
            g.source = Source.query.filter(Source.filesystem_id == g.sid).one()
        except MultipleResultsFound as e:
            app.logger.error("Found multiple Sources when one was expected: %s" % (e,))
            abort(500)
        except NoResultFound as e:
            app.logger.error("Found no Sources when one was expected: %s" % (e,))
            abort(404)
        g.loc = store.path(g.sid)
Beispiel #17
0
def valid_codename(codename):
    # Ignore codenames that are too long to avoid DoS
    if len(codename) > Source.MAX_CODENAME_LEN:
        app.logger.info(
            "Ignored attempted login because the codename was too long.")
        return False

    try:
        filesystem_id = crypto_util.hash_codename(codename)
    except crypto_util.CryptoException as e:
        app.logger.info(
            "Could not compute filesystem ID for codename '{}': {}".format(
                codename, e))
        abort(500)

    source = Source.query.filter_by(filesystem_id=filesystem_id).first()
    return source is not None
Beispiel #18
0
def setup_g():
    """Store commonly used values in Flask's special g object"""
    # ignore_static here because `crypto_util.hash_codename` is scrypt (very
    # time consuming), and we don't need to waste time running if we're just
    # serving a static resource that won't need to access these common values.
    if logged_in():
        g.codename = session['codename']
        g.sid = crypto_util.hash_codename(g.codename)
        try:
            g.source = Source.query.filter(Source.filesystem_id == g.sid).one()
        except MultipleResultsFound as e:
            app.logger.error("Found multiple Sources when one was expected: %s" % (e,))
            abort(500)
        except NoResultFound as e:
            app.logger.error("Found no Sources when one was expected: %s" % (e,))
            del session['logged_in']
            del session['codename']
            return redirect(url_for('index'))
        g.loc = store.path(g.sid)
Beispiel #19
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:`db.Source`
    initialized. The second, their codename string.
    """
    # Create source identity and database record
    codename = crypto_util.genrandomid()
    filesystem_id = crypto_util.hash_codename(codename)
    journalist_filename = crypto_util.display_id()
    source = db.Source(filesystem_id, journalist_filename)
    db.db_session.add(source)
    db.db_session.commit()
    # Create the directory to store their submissions and replies
    os.mkdir(store.path(source.filesystem_id))

    return source, codename
Beispiel #20
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:`db.Source`
    initialized. The second, their codename string.
    """
    # Create source identity and database record
    codename = crypto_util.genrandomid()
    filesystem_id = crypto_util.hash_codename(codename)
    journalist_filename = crypto_util.display_id()
    source = db.Source(filesystem_id, journalist_filename)
    db.db_session.add(source)
    db.db_session.commit()
    # Create the directory to store their submissions and replies
    os.mkdir(store.path(source.filesystem_id))

    return source, codename
Beispiel #21
0
    def create():
        filesystem_id = crypto_util.hash_codename(session['codename'])

        source = Source(filesystem_id, crypto_util.display_id())
        db_session.add(source)
        try:
            db_session.commit()
        except IntegrityError as e:
            db_session.rollback()
            current_app.logger.error(
                "Attempt to create a source with duplicate codename: %s" %
                (e, ))

            # Issue 2386: don't log in on duplicates
            del session['codename']
            abort(500)
        else:
            os.mkdir(store.path(filesystem_id))

        session['logged_in'] = True
        return redirect(url_for('.lookup'))
Beispiel #22
0
    def setup_g():
        """Store commonly used values in Flask's special g object"""
        g.locale = i18n.get_locale(config)
        g.text_direction = i18n.get_text_direction(g.locale)
        g.html_lang = i18n.locale_to_rfc_5646(g.locale)
        g.locales = i18n.get_locale2name()

        if 'expires' in session and datetime.utcnow() >= session['expires']:
            msg = render_template('session_timeout.html')

            # clear the session after we render the message so it's localized
            session.clear()

            flash(Markup(msg), "important")

        session['expires'] = datetime.utcnow() + \
            timedelta(minutes=getattr(config,
                                      'SESSION_EXPIRATION_MINUTES',
                                      120))

        # ignore_static here because `crypto_util.hash_codename` is scrypt
        # (very time consuming), and we don't need to waste time running if
        # we're just serving a static resource that won't need to access
        # these common values.
        if logged_in():
            g.codename = session['codename']
            g.filesystem_id = crypto_util.hash_codename(g.codename)
            try:
                g.source = Source.query \
                            .filter(Source.filesystem_id == g.filesystem_id) \
                            .one()
            except NoResultFound as e:
                app.logger.error(
                    "Found no Sources when one was expected: %s" %
                    (e,))
                del session['logged_in']
                del session['codename']
                return redirect(url_for('main.index'))
            g.loc = store.path(g.filesystem_id)
Beispiel #23
0
    def setup_g():
        """Store commonly used values in Flask's special g object"""
        g.locale = i18n.get_locale(config)
        g.text_direction = i18n.get_text_direction(g.locale)
        g.html_lang = i18n.locale_to_rfc_5646(g.locale)
        g.locales = i18n.get_locale2name()

        if 'expires' in session and datetime.utcnow() >= session['expires']:
            msg = render_template('session_timeout.html')

            # clear the session after we render the message so it's localized
            session.clear()

            flash(Markup(msg), "important")

        session['expires'] = datetime.utcnow() + \
            timedelta(minutes=getattr(config,
                                      'SESSION_EXPIRATION_MINUTES',
                                      120))

        # ignore_static here because `crypto_util.hash_codename` is scrypt
        # (very time consuming), and we don't need to waste time running if
        # we're just serving a static resource that won't need to access
        # these common values.
        if logged_in():
            g.codename = session['codename']
            g.filesystem_id = crypto_util.hash_codename(g.codename)
            try:
                g.source = Source.query \
                            .filter(Source.filesystem_id == g.filesystem_id) \
                            .one()
            except NoResultFound as e:
                app.logger.error("Found no Sources when one was expected: %s" %
                                 (e, ))
                del session['logged_in']
                del session['codename']
                return redirect(url_for('main.index'))
            g.loc = store.path(g.filesystem_id)
Beispiel #24
0
def generate_unique_codename():
    """Generate random codenames until we get an unused one"""
    while True:
        codename = crypto_util.genrandomid(Source.NUM_WORDS)

        # The maximum length of a word in the wordlist is 9 letters and the
        # codename length is 7 words, so it is currently impossible to
        # generate a codename that is longer than the maximum codename length
        # (currently 128 characters). This code is meant to be defense in depth
        # to guard against potential future changes, such as modifications to
        # the word list or the maximum codename length.
        if len(codename) > Source.MAX_CODENAME_LEN:
            app.logger.warning(
                "Generated a source codename that was too long, "
                "skipping it. This should not happen. "
                "(Codename='{}')".format(codename))
            continue

        filesystem_id = crypto_util.hash_codename(codename)  # scrypt (slow)
        matching_sources = Source.query.filter(
            Source.filesystem_id == filesystem_id).all()
        if len(matching_sources) == 0:
            return codename
 def _source_delete_key(self):
     filesystem_id = crypto_util.hash_codename(self.source_name)
     crypto_util.delete_reply_keypair(filesystem_id)
Beispiel #26
0
def _block_on_reply_keypair_gen(codename):
    sid = crypto_util.hash_codename(codename)
    while not crypto_util.getkey(sid):
        sleep(0.1)
Beispiel #27
0
def valid_codename(codename):
    return os.path.exists(store.path(crypto_util.hash_codename(codename)))
    def test_hash_codename(self):
        codename = crypto_util.genrandomid()
        hashed_codename = crypto_util.hash_codename(codename)

        self.assertRegexpMatches(hashed_codename, '^[2-7A-Z]{103}=$')
Beispiel #29
0
def valid_codename(codename):
    return os.path.exists(store.path(crypto_util.hash_codename(codename)))
 def _source_delete_key(self):
     filesystem_id = crypto_util.hash_codename(self.source_name)
     crypto_util.delete_reply_keypair(filesystem_id)
def _block_on_reply_keypair_gen(codename):
    sid = crypto_util.hash_codename(codename)
    while not crypto_util.getkey(sid):
        sleep(0.1)