Exemple #1
0
def lookup():
    replies = []
    for fn in os.listdir(g.loc):
        if fn.startswith('reply-'):
            try:
                msg = crypto_util.decrypt(g.sid, g.codename,
                        file(store.path(g.sid, fn)).read()).decode("utf-8")
            except UnicodeDecodeError:
                app.logger.error("Could not decode reply %s" % fn)
            else:
                date = str(datetime.fromtimestamp(
                           os.stat(store.path(g.sid, fn)).st_mtime))
                replies.append(dict(id=fn, date=date, msg=msg))

    def async_genkey(sid, codename):
        with app.app_context():
            background.execute(lambda: crypto_util.genkeypair(sid, codename))

    # Generate a keypair to encrypt replies from the journalist
    # Only do this if the journalist has flagged the source as one
    # that they would like to reply to. (Issue #140.)
    if not crypto_util.getkey(g.sid) and g.source.flagged:
        async_genkey(g.sid, g.codename)

    return render_template('lookup.html', codename=g.codename, msgs=replies,
            flagged=g.source.flagged, haskey=crypto_util.getkey(g.sid))
Exemple #2
0
def lookup():
    msgs = []
    flagged = False
    for fn in os.listdir(g.loc):
        if fn == '_FLAG':
            flagged = True
            continue
        if fn.startswith('reply-'):
            msgs.append(dict(
                id=fn,
                date=str(
                    datetime.fromtimestamp(
                        os.stat(store.path(g.sid, fn)).st_mtime)),
                msg=crypto_util.decrypt(
                    g.sid, g.codename, file(store.path(g.sid, fn)).read())
            ))
    if flagged:
        session['flagged'] = True

    def async_genkey(sid, codename):
        with app.app_context():
            background.execute(lambda: crypto_util.genkeypair(sid, codename))

    # Generate a keypair to encrypt replies from the journalist
    # Only do this if the journalist has flagged the source as one
    # that they would like to reply to. (Issue #140.)
    if not crypto_util.getkey(g.sid) and flagged:
        async_genkey(g.sid, g.codename)

    return render_template(
        'lookup.html', codename=g.codename, msgs=msgs, flagged=flagged,
        haskey=crypto_util.getkey(g.sid))
Exemple #3
0
def lookup():
    replies = []
    for reply in g.source.replies:
        reply_path = store.path(g.filesystem_id, reply.filename)
        try:
            reply.decrypted = crypto_util.decrypt(
                g.codename,
                open(reply_path).read()).decode('utf-8')
        except UnicodeDecodeError:
            app.logger.error("Could not decode reply %s" % reply.filename)
        else:
            reply.date = datetime.utcfromtimestamp(
                os.stat(reply_path).st_mtime)
            replies.append(reply)

    # Sort the replies by date
    replies.sort(key=operator.attrgetter('date'), reverse=True)

    # Generate a keypair to encrypt replies from the journalist
    # Only do this if the journalist has flagged the source as one
    # that they would like to reply to. (Issue #140.)
    if not crypto_util.getkey(g.filesystem_id) and g.source.flagged:
        async_genkey(g.filesystem_id, g.codename)

    return render_template('lookup.html',
                           codename=g.codename,
                           replies=replies,
                           flagged=g.source.flagged,
                           haskey=crypto_util.getkey(g.filesystem_id))
Exemple #4
0
    def test_delete_source_deletes_source_key(self):
        """Verify that when a source is deleted, the PGP key that corresponds
        to them is also deleted."""
        self._delete_collection_setup()

        # Source key exists
        source_key = crypto_util.getkey(self.source.filesystem_id)
        self.assertNotEqual(source_key, None)

        journalist.delete_collection(self.source.filesystem_id)

        # Source key no longer exists
        source_key = crypto_util.getkey(self.source.filesystem_id)
        self.assertEqual(source_key, None)
def reply(journalist, source, num_replies):
    """Generates and submits *num_replies* replies to *source*
    from *journalist*. Returns reply objects as a list.

    :param db.Journalist journalist: The journalist to write the
                                     reply from.

    :param db.Source source: The source to send the reply to.

    :param int num_replies: Number of random-data replies to make.

    :returns: A list of the :class:`db.Reply`s submitted.
    """
    assert num_replies >= 1
    replies = []
    for _ in range(num_replies):
        source.interaction_count += 1
        fname = "{}-{}-reply.gpg".format(source.interaction_count,
                                         source.journalist_filename)
        crypto_util.encrypt(str(os.urandom(1)),
                            [
                                crypto_util.getkey(source.filesystem_id),
                                config.JOURNALIST_KEY
                            ],
                            store.path(source.filesystem_id, fname))
        reply = db.Reply(journalist, source, fname)
        replies.append(reply)
        db.db_session.add(reply)

    db.db_session.commit()
    return replies
Exemple #6
0
def col(sid):
    source = get_source(sid)
    docs = get_docs(sid)
    haskey = crypto_util.getkey(sid)
    return render_template("col.html", sid=sid,
            codename=source.journalist_designation, docs=docs, haskey=haskey,
            flagged=source.flagged)
Exemple #7
0
def reply(journalist, source, num_replies):
    """Generates and submits *num_replies* replies to *source*
    from *journalist*. Returns reply objects as a list.

    :param db.Journalist journalist: The journalist to write the
                                     reply from.

    :param db.Source source: The source to send the reply to.

    :param int num_replies: Number of random-data replies to make.

    :returns: A list of the :class:`db.Reply`s submitted.
    """
    assert num_replies >= 1
    replies = []
    for _ in range(num_replies):
        source.interaction_count += 1
        fname = "{}-{}-reply.gpg".format(source.interaction_count,
                                         source.journalist_filename)
        crypto_util.encrypt(
            str(os.urandom(1)),
            [crypto_util.getkey(source.filesystem_id), config.JOURNALIST_KEY],
            store.path(source.filesystem_id, fname))
        reply = db.Reply(journalist, source, fname)
        replies.append(reply)
        db.db_session.add(reply)

    db.db_session.commit()
    return replies
Exemple #8
0
 def col(filesystem_id):
     form = ReplyForm()
     source = get_source(filesystem_id)
     source.has_key = crypto_util.getkey(filesystem_id)
     return render_template("col.html",
                            filesystem_id=filesystem_id,
                            source=source,
                            form=form)
Exemple #9
0
def reply():
    sid, msg_candidate = request.form["sid"], request.form["msg"]
    try:
        msg = msg_candidate.decode()
    except (UnicodeDecodeError, UnicodeEncodeError):
        flash("You have entered text that we could not parse. Please try again.", "notification")
        return render_template("col.html", sid=sid, codename=db.display_id(sid, db.sqlalchemy_handle()))
    crypto_util.encrypt(crypto_util.getkey(sid), msg, output=store.path(sid, "reply-%s.gpg" % uuid.uuid4()))
    return render_template("reply.html", sid=sid, codename=db.display_id(sid, db.sqlalchemy_handle()))
Exemple #10
0
def col(sid):
    docs, flagged = get_docs(sid)
    haskey = crypto_util.getkey(sid)
    return render_template("col.html",
                           sid=sid,
                           codename=db.display_id(sid, db.sqlalchemy_handle()),
                           docs=docs,
                           haskey=haskey,
                           flagged=flagged)
    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))
Exemple #12
0
def col(sid):
    docs, flagged = get_docs(sid)
    haskey = crypto_util.getkey(sid)
    return render_template(
        "col.html",
        sid=sid,
        codename=db.display_id(sid, db.sqlalchemy_handle()),
        docs=docs,
        haskey=haskey,
        flagged=flagged,
    )
    def test_encrypt_success(self):
        source, _ = utils.db_helper.init_source()
        message = str(os.urandom(1))
        ciphertext = crypto_util.encrypt(
            message,
            [crypto_util.getkey(source.filesystem_id), config.JOURNALIST_KEY],
            store.path(source.filesystem_id, 'somefile.gpg'))

        self.assertIsInstance(ciphertext, str)
        self.assertNotEqual(ciphertext, message)
        self.assertGreater(len(ciphertext), 0)
Exemple #14
0
def lookup():
    msgs = []
    flagged = False
    for fn in os.listdir(g.loc):
        # TODO: make 'flag' a db column, so we can replace this with a db
        # lookup in the future
        if fn == '_FLAG':
            flagged = True
            continue
        if fn.startswith('reply-'):
            msg_candidate = crypto_util.decrypt(
                g.sid, g.codename,
                file(store.path(g.sid, fn)).read())
            try:
                msgs.append(
                    dict(id=fn,
                         date=str(
                             datetime.fromtimestamp(
                                 os.stat(store.path(g.sid, fn)).st_mtime)),
                         msg=msg_candidate.decode()))
            except UnicodeDecodeError:
                # todo: we should have logging here!
                pass
    if flagged:
        session['flagged'] = True

    def async_genkey(sid, codename):
        with app.app_context():
            background.execute(lambda: crypto_util.genkeypair(sid, codename))

    # Generate a keypair to encrypt replies from the journalist
    # Only do this if the journalist has flagged the source as one
    # that they would like to reply to. (Issue #140.)
    if not crypto_util.getkey(g.sid) and flagged:
        async_genkey(g.sid, g.codename)

    return render_template('lookup.html',
                           codename=g.codename,
                           msgs=msgs,
                           flagged=flagged,
                           haskey=crypto_util.getkey(g.sid))
Exemple #15
0
def reply():
    g.source.interaction_count += 1
    filename = "{0}-{1}-reply.gpg".format(g.source.interaction_count, g.source.journalist_filename)
    crypto_util.encrypt(
        request.form["msg"], [crypto_util.getkey(g.sid), config.JOURNALIST_KEY], output=store.path(g.sid, filename)
    )
    reply = Reply(g.user, g.source, filename)
    db_session.add(reply)
    db_session.commit()

    flash("Thanks! Your reply has been stored.", "notification")
    return redirect(url_for("col", sid=g.sid))
Exemple #16
0
    def test_encrypt_fingerprints_not_a_list_or_tuple(self):
        """If passed a single fingerprint as a string, encrypt should
        correctly place that string in a list, and encryption/
        decryption should work as intended."""
        source, codename = utils.db_helper.init_source()
        message = str(os.urandom(1))
        ciphertext = crypto_util.encrypt(
            message, crypto_util.getkey(source.filesystem_id),
            store.path(source.filesystem_id, 'somefile.gpg'))
        plaintext = crypto_util.decrypt(codename, ciphertext)

        self.assertEqual(message, plaintext)
    def test_encrypt_without_output(self):
        """We simply do not specify the option output keyword argument
        to crypto_util.encrypt() here in order to confirm encryption
        works when it defaults to `None`.
        """
        source, codename = utils.db_helper.init_source()
        message = str(os.urandom(1))
        ciphertext = crypto_util.encrypt(
            message,
            [crypto_util.getkey(source.filesystem_id), config.JOURNALIST_KEY])
        plaintext = crypto_util.decrypt(codename, ciphertext)

        self.assertEqual(message, plaintext)
Exemple #18
0
def reply():
    g.source.interaction_count += 1
    filename = "{0}-{1}-reply.gpg".format(g.source.interaction_count,
                                          g.source.journalist_filename)
    crypto_util.encrypt(request.form['msg'],
                        [crypto_util.getkey(g.sid), config.JOURNALIST_KEY],
                        output=store.path(g.sid, filename))
    reply = Reply(g.user, g.source, filename)
    db_session.add(reply)
    db_session.commit()

    flash("Thanks! Your reply has been stored.", "notification")
    return redirect(url_for('col', sid=g.sid))
Exemple #19
0
def lookup():
    msgs = []
    flagged = False
    for fn in os.listdir(g.loc):
        # TODO: make 'flag' a db column, so we can replace this with a db
        # lookup in the future
        if fn == '_FLAG':
            flagged = True
            continue
        if fn.startswith('reply-'):
            msg_candidate = crypto_util.decrypt(
                g.sid, g.codename, file(store.path(g.sid, fn)).read())
            try:
                msgs.append(dict(
                        id=fn,
                        date=str(
                            datetime.fromtimestamp(
                                os.stat(store.path(g.sid, fn)).st_mtime)),
                        msg=msg_candidate.decode()))
            except UnicodeDecodeError:
                # todo: we should have logging here!
                pass
    if flagged:
        session['flagged'] = True

    def async_genkey(sid, codename):
        with app.app_context():
            background.execute(lambda: crypto_util.genkeypair(sid, codename))

    # Generate a keypair to encrypt replies from the journalist
    # Only do this if the journalist has flagged the source as one
    # that they would like to reply to. (Issue #140.)
    if not crypto_util.getkey(g.sid) and flagged:
        async_genkey(g.sid, g.codename)

    return render_template(
        'lookup.html', codename=g.codename, msgs=msgs, flagged=flagged,
        haskey=crypto_util.getkey(g.sid))
Exemple #20
0
    def reply():
        """Attempt to send a Reply from a Journalist to a Source. Empty
        messages are rejected, and an informative error message is flashed
        on the client. In the case of unexpected errors involving database
        transactions (potentially caused by racing request threads that
        modify the same the database object) logging is done in such a way
        so as not to write potentially sensitive information to disk, and a
        generic error message is flashed on the client.

        Returns:
           flask.Response: The user is redirected to the same Source
               collection view, regardless if the Reply is created
               successfully.
        """
        form = ReplyForm()
        if not form.validate_on_submit():
            for error in form.message.errors:
                flash(error, "error")
            return redirect(url_for('col.col', filesystem_id=g.filesystem_id))

        g.source.interaction_count += 1
        filename = "{0}-{1}-reply.gpg".format(g.source.interaction_count,
                                              g.source.journalist_filename)
        crypto_util.encrypt(form.message.data,
                            [crypto_util.getkey(g.filesystem_id),
                             config.JOURNALIST_KEY],
                            output=store.path(g.filesystem_id, filename))
        reply = Reply(g.user, g.source, filename)

        try:
            db_session.add(reply)
            db_session.commit()
        except Exception as exc:
            flash(gettext(
                "An unexpected error occurred! Please "
                "inform your administrator."), "error")
            # We take a cautious approach to logging here because we're dealing
            # with responses to sources. It's possible the exception message
            # could contain information we don't want to write to disk.
            current_app.logger.error(
                "Reply from '{}' (ID {}) failed: {}!".format(g.user.username,
                                                             g.user.id,
                                                             exc.__class__))
        else:
            flash(gettext("Thanks. Your reply has been stored."),
                  "notification")
        finally:
            return redirect(url_for('col.col', filesystem_id=g.filesystem_id))
Exemple #21
0
    def reply():
        """Attempt to send a Reply from a Journalist to a Source. Empty
        messages are rejected, and an informative error message is flashed
        on the client. In the case of unexpected errors involving database
        transactions (potentially caused by racing request threads that
        modify the same the database object) logging is done in such a way
        so as not to write potentially sensitive information to disk, and a
        generic error message is flashed on the client.

        Returns:
           flask.Response: The user is redirected to the same Source
               collection view, regardless if the Reply is created
               successfully.
        """
        form = ReplyForm()
        if not form.validate_on_submit():
            for error in form.message.errors:
                flash(error, "error")
            return redirect(url_for('col.col', filesystem_id=g.filesystem_id))

        g.source.interaction_count += 1
        filename = "{0}-{1}-reply.gpg".format(g.source.interaction_count,
                                              g.source.journalist_filename)
        crypto_util.encrypt(
            form.message.data,
            [crypto_util.getkey(g.filesystem_id), config.JOURNALIST_KEY],
            output=store.path(g.filesystem_id, filename))
        reply = Reply(g.user, g.source, filename)

        try:
            db_session.add(reply)
            db_session.commit()
        except Exception as exc:
            flash(
                gettext("An unexpected error occurred! Please "
                        "inform your administrator."), "error")
            # We take a cautious approach to logging here because we're dealing
            # with responses to sources. It's possible the exception message
            # could contain information we don't want to write to disk.
            current_app.logger.error(
                "Reply from '{}' (ID {}) failed: {}!".format(
                    g.user.username, g.user.id, exc.__class__))
        else:
            flash(gettext("Thanks. Your reply has been stored."),
                  "notification")
        finally:
            return redirect(url_for('col.col', filesystem_id=g.filesystem_id))
Exemple #22
0
def reply():
    sid, msg_candidate = request.form['sid'], request.form['msg']
    try:
        msg = msg_candidate.decode()
    except (UnicodeDecodeError, UnicodeEncodeError):
        flash(
            "You have entered text that we could not parse. Please try again.",
            "notification")
        return render_template('col.html',
                               sid=sid,
                               codename=db.display_id(sid,
                                                      db.sqlalchemy_handle()))
    crypto_util.encrypt(crypto_util.getkey(sid),
                        msg,
                        output=store.path(sid, 'reply-%s.gpg' % uuid.uuid4()))
    return render_template('reply.html',
                           sid=sid,
                           codename=db.display_id(sid, db.sqlalchemy_handle()))
Exemple #23
0
    def test_basic_encrypt_then_decrypt_multiple_recipients(self):
        source, codename = utils.db_helper.init_source()
        message = str(os.urandom(1))
        ciphertext = crypto_util.encrypt(
            message,
            [crypto_util.getkey(source.filesystem_id), config.JOURNALIST_KEY],
            store.path(source.filesystem_id, 'somefile.gpg'))
        plaintext = crypto_util.decrypt(codename, ciphertext)

        self.assertEqual(message, plaintext)

        # Since there's no way to specify which key to use for
        # decryption to python-gnupg, we delete the `source`'s key and
        # ensure we can decrypt with the `config.JOURNALIST_KEY`.
        crypto_util.delete_reply_keypair(source.filesystem_id)
        plaintext_ = crypto_util.gpg.decrypt(ciphertext).data

        self.assertEqual(message, plaintext_)
    def test_encrypt_binary_stream(self):
        """Generally, we pass unicode strings (the type form data is
        returned as) as plaintext to crypto_util.encrypt(). These have
        to be converted to "binary stream" types (such as `file`) before
        we can actually call gnupg.GPG.encrypt() on them. This is done
        in crypto_util.encrypt() with an `if` branch that uses
        `gnupg._util._is_stream(plaintext)` as the predicate, and calls
        `gnupg._util._make_binary_stream(plaintext)` if necessary. This
        test ensures our encrypt function works even if we provide
        inputs such that this `if` branch is skipped (i.e., the object
        passed for `plaintext` is one such that
        `gnupg._util._is_stream(plaintext)` returns `True`).
        """
        source, codename = utils.db_helper.init_source()
        with open(os.path.realpath(__file__)) as fh:
            ciphertext = crypto_util.encrypt(
                fh,
                [crypto_util.getkey(source.filesystem_id),
                 config.JOURNALIST_KEY],
                store.path(source.filesystem_id, 'somefile.gpg'))
        plaintext = crypto_util.decrypt(codename, ciphertext)

        with open(os.path.realpath(__file__)) as fh:
            self.assertEqual(fh.read(), plaintext)
    def test_delete_reply_keypair(self):
        source, _ = utils.db_helper.init_source()
        crypto_util.delete_reply_keypair(source.filesystem_id)

        self.assertIsNone(crypto_util.getkey(source.filesystem_id))
Exemple #26
0
 def key_available(filesystem_id):
     assert crypto_util.getkey(filesystem_id)
Exemple #27
0
 def col(filesystem_id):
     form = ReplyForm()
     source = get_source(filesystem_id)
     source.has_key = crypto_util.getkey(filesystem_id)
     return render_template("col.html", filesystem_id=filesystem_id,
                            source=source, form=form)
Exemple #28
0
def _block_on_reply_keypair_gen(codename):
    sid = crypto_util.shash(codename)
    while not crypto_util.getkey(sid):
        sleep(0.1)
Exemple #29
0
def reply():
    msg = request.form['msg']
    crypto_util.encrypt(crypto_util.getkey(g.sid), msg, output=
                        store.path(g.sid, 'reply-%s.gpg' % uuid.uuid4()))
    return render_template('reply.html', sid=g.sid,
            codename=g.source.journalist_designation)
    def helper_test_reply(self, test_reply, expected_success=True):
        test_msg = "This is a test message."

        with self.source_app as source_app:
            resp = source_app.get('/generate')
            resp = source_app.post('/create', follow_redirects=True)
            codename = session['codename']
            filesystem_id = g.filesystem_id
            # redirected to submission form
            resp = source_app.post('/submit', data=dict(
                msg=test_msg,
                fh=(StringIO(''), ''),
            ), follow_redirects=True)
            self.assertEqual(resp.status_code, 200)
            self.assertFalse(g.source.flagged)
            source_app.get('/logout')

        resp = self.journalist_app.get('/')
        self.assertEqual(resp.status_code, 200)
        self.assertIn("Sources", resp.data)
        soup = BeautifulSoup(resp.data, 'html.parser')
        col_url = soup.select('ul#cols > li a')[0]['href']

        resp = self.journalist_app.get(col_url)
        self.assertEqual(resp.status_code, 200)

        with self.source_app as source_app:
            resp = source_app.post('/login', data=dict(
                codename=codename), follow_redirects=True)
            self.assertEqual(resp.status_code, 200)
            self.assertFalse(g.source.flagged)
            source_app.get('/logout')

        with self.journalist_app as journalist_app:
            resp = journalist_app.post('/flag', data=dict(
                filesystem_id=filesystem_id))
            self.assertEqual(resp.status_code, 200)

        with self.source_app as source_app:
            resp = source_app.post('/login', data=dict(
                codename=codename), follow_redirects=True)
            self.assertEqual(resp.status_code, 200)
            self.assertTrue(g.source.flagged)
            source_app.get('/lookup')
            self.assertTrue(g.source.flagged)
            source_app.get('/logout')

        # Block up to 15s for the reply keypair, so we can test sending a reply
        utils.async.wait_for_assertion(
            lambda: self.assertNotEqual(crypto_util.getkey(filesystem_id),
                                        None),
            15)

        # Create 2 replies to test deleting on journalist and source interface
        for i in range(2):
            resp = self.journalist_app.post('/reply', data=dict(
                filesystem_id=filesystem_id,
                message=test_reply
            ), follow_redirects=True)
            self.assertEqual(resp.status_code, 200)

        if not expected_success:
            pass
        else:
            self.assertIn("Thanks. Your reply has been stored.", resp.data)

        with self.journalist_app as journalist_app:
            resp = journalist_app.get(col_url)
            self.assertIn("reply-", resp.data)

        soup = BeautifulSoup(resp.data, 'html.parser')

        # Download the reply and verify that it can be decrypted with the
        # journalist's key as well as the source's reply key
        filesystem_id = soup.select('input[name="filesystem_id"]')[0]['value']
        checkbox_values = [
            soup.select('input[name="doc_names_selected"]')[1]['value']]
        resp = self.journalist_app.post('/bulk', data=dict(
            filesystem_id=filesystem_id,
            action='download',
            doc_names_selected=checkbox_values
        ), follow_redirects=True)
        self.assertEqual(resp.status_code, 200)

        zf = zipfile.ZipFile(StringIO(resp.data), 'r')
        data = zf.read(zf.namelist()[0])
        self._can_decrypt_with_key(data, config.JOURNALIST_KEY)
        self._can_decrypt_with_key(data, crypto_util.getkey(filesystem_id),
                                   codename)

        # Test deleting reply on the journalist interface
        last_reply_number = len(
            soup.select('input[name="doc_names_selected"]')) - 1
        self.helper_filenames_delete(soup, last_reply_number)

        with self.source_app as source_app:
            resp = source_app.post('/login', data=dict(codename=codename),
                                   follow_redirects=True)
            self.assertEqual(resp.status_code, 200)
            resp = source_app.get('/lookup')
            self.assertEqual(resp.status_code, 200)

            if not expected_success:
                # there should be no reply
                self.assertNotIn("You have received a reply.", resp.data)
            else:
                self.assertIn(
                    "You have received a reply. To protect your identity",
                    resp.data)
                self.assertIn(test_reply, resp.data)
                soup = BeautifulSoup(resp.data, 'html.parser')
                msgid = soup.select(
                    'form.message > input[name="reply_filename"]')[0]['value']
                resp = source_app.post('/delete', data=dict(
                    filesystem_id=filesystem_id,
                    reply_filename=msgid
                ), follow_redirects=True)
                self.assertEqual(resp.status_code, 200)
                self.assertIn("Reply deleted", resp.data)

                # Make sure the reply is deleted from the filesystem
                utils.async.wait_for_assertion(
                    lambda: self.assertFalse(os.path.exists(
                        store.path(filesystem_id, msgid))))

            source_app.get('/logout')
Exemple #31
0
def reply():
    sid, msg = request.form['sid'], request.form['msg']
    crypto_util.encrypt(crypto_util.getkey(sid), request.form['msg'], output=
                        store.path(sid, 'reply-%s.gpg' % uuid.uuid4()))
    return render_template('reply.html', sid=sid, codename=db.display_id(sid, db.sqlalchemy_handle()))
Exemple #32
0
def col(sid):
    source = get_source(sid)
    source.has_key = crypto_util.getkey(sid)
    return render_template("col.html", sid=sid, source=source)
def _block_on_reply_keypair_gen(codename):
    sid = crypto_util.hash_codename(codename)
    while not crypto_util.getkey(sid):
        sleep(0.1)
Exemple #34
0
def col(sid):
    source = get_source(sid)
    source.has_key = crypto_util.getkey(sid)
    return render_template("col.html", sid=sid, source=source)
 def key_available(filesystem_id):
     assert crypto_util.getkey(filesystem_id)
Exemple #36
0
    def helper_test_reply(self, test_reply, expected_success=True):
        test_msg = "This is a test message."

        with self.source_app as source_app:
            resp = source_app.get('/generate')
            resp = source_app.post('/create', follow_redirects=True)
            codename = session['codename']
            sid = g.sid
            # redirected to submission form
            resp = source_app.post('/submit',
                                   data=dict(
                                       msg=test_msg,
                                       fh=(StringIO(''), ''),
                                   ),
                                   follow_redirects=True)
            self.assertEqual(resp.status_code, 200)
            self.assertFalse(g.source.flagged)
            source_app.get('/logout')

        resp = self.journalist_app.get('/')
        self.assertEqual(resp.status_code, 200)
        self.assertIn("Sources", resp.data)
        soup = BeautifulSoup(resp.data)
        col_url = soup.select('ul#cols > li a')[0]['href']

        resp = self.journalist_app.get(col_url)
        self.assertEqual(resp.status_code, 200)

        with self.source_app as source_app:
            resp = source_app.post('/login',
                                   data=dict(codename=codename),
                                   follow_redirects=True)
            self.assertEqual(resp.status_code, 200)
            self.assertFalse(g.source.flagged)
            source_app.get('/logout')

        with self.journalist_app as journalist_app:
            resp = journalist_app.post('/flag', data=dict(sid=sid))
            self.assertEqual(resp.status_code, 200)

        with self.source_app as source_app:
            resp = source_app.post('/login',
                                   data=dict(codename=codename),
                                   follow_redirects=True)
            self.assertEqual(resp.status_code, 200)
            self.assertTrue(g.source.flagged)
            source_app.get('/lookup')
            self.assertTrue(g.source.flagged)
            source_app.get('/logout')

        # Block up to 15s for the reply keypair, so we can test sending a reply
        utils. async .wait_for_assertion(
            lambda: self.assertNotEqual(crypto_util.getkey(sid), None), 15)

        # Create 2 replies to test deleting on journalist and source interface
        for i in range(2):
            resp = self.journalist_app.post('/reply',
                                            data=dict(sid=sid, msg=test_reply),
                                            follow_redirects=True)
            self.assertEqual(resp.status_code, 200)

        if not expected_success:
            pass
        else:
            self.assertIn("Thanks! Your reply has been stored.", resp.data)

        with self.journalist_app as journalist_app:
            resp = journalist_app.get(col_url)
            self.assertIn("reply-", resp.data)

        soup = BeautifulSoup(resp.data)

        # Download the reply and verify that it can be decrypted with the
        # journalist's key as well as the source's reply key
        sid = soup.select('input[name="sid"]')[0]['value']
        checkbox_values = [
            soup.select('input[name="doc_names_selected"]')[1]['value']
        ]
        resp = self.journalist_app.post(
            '/bulk',
            data=dict(sid=sid,
                      action='download',
                      doc_names_selected=checkbox_values),
            follow_redirects=True)
        self.assertEqual(resp.status_code, 200)

        zf = zipfile.ZipFile(StringIO(resp.data), 'r')
        data = zf.read(zf.namelist()[0])
        self._can_decrypt_with_key(data, config.JOURNALIST_KEY)
        self._can_decrypt_with_key(data, crypto_util.getkey(sid), codename)

        # Test deleting reply on the journalist interface
        last_reply_number = len(
            soup.select('input[name="doc_names_selected"]')) - 1
        self.helper_filenames_delete(soup, last_reply_number)

        with self.source_app as source_app:
            resp = source_app.post('/login',
                                   data=dict(codename=codename),
                                   follow_redirects=True)
            self.assertEqual(resp.status_code, 200)
            resp = source_app.get('/lookup')
            self.assertEqual(resp.status_code, 200)

            if not expected_success:
                # there should be no reply
                self.assertNotIn("You have received a reply.", resp.data)
            else:
                self.assertIn(
                    "You have received a reply. To protect your identity",
                    resp.data)
                self.assertIn(test_reply, resp.data)
                soup = BeautifulSoup(resp.data)
                msgid = soup.select(
                    'form.message > input[name="reply_filename"]')[0]['value']
                resp = source_app.post('/delete',
                                       data=dict(sid=sid,
                                                 reply_filename=msgid),
                                       follow_redirects=True)
                self.assertEqual(resp.status_code, 200)
                self.assertIn("Reply deleted", resp.data)

                # Make sure the reply is deleted from the filesystem
                utils. async .wait_for_assertion(lambda: self.assertFalse(
                    os.path.exists(store.path(sid, msgid))))

            source_app.get('/logout')
    def test_getkey(self):
        source, _ = utils.db_helper.init_source()

        self.assertIsNotNone(crypto_util.getkey(source.filesystem_id))