Ejemplo n.º 1
0
def delete_user(args: argparse.Namespace,
                context: Optional[AppContext] = None) -> int:
    """Deletes a journalist or admin from the application."""
    with context or app_context():
        username = _get_username_to_delete()
        try:
            selected_user = Journalist.query.filter_by(username=username).one()
        except NoResultFound:
            print("ERROR: That user was not found!")
            return 0

        # Confirm deletion if user is found
        if not _get_delete_confirmation(selected_user.username):
            return 0

        # Try to delete user from the database
        try:
            db.session.delete(selected_user)
            db.session.commit()
        except Exception as e:
            # If the user was deleted between the user selection and
            # confirmation, (e.g., through the web app), we don't report any
            # errors. If the user is still there, but there was a error
            # deleting them from the database, we do report it.
            try:
                Journalist.query.filter_by(username=username).one()
            except NoResultFound:
                pass
            else:
                raise e

        print('User "{}" successfully deleted'.format(username))
    return 0
Ejemplo n.º 2
0
def delete_disconnected_fs_submissions(args):
    """
    Delete files without a corresponding Submission record in the database.
    """
    with app_context():
        disconnected_files = find_disconnected_fs_submissions(args.store_dir)
        bytes_deleted = 0
        time_elapsed = 0.0
        rate = 1.0
        filecount = len(disconnected_files)
        eta = 1.0
        eta_msg = ""
        for i, f in enumerate(disconnected_files, 1):
            remove = args.force
            if not args.force:
                remove = input("Enter 'y' to delete {}: ".format(f)) == "y"
            if remove:
                filesize = os.stat(f).st_size
                if i > 1:
                    eta = filesize / rate
                    eta_msg = " (ETA to remove {:d} bytes: {:.0f}s )".format(filesize, eta)
                print("Securely removing file {}/{} {}{}...".format(i, filecount, f, eta_msg))
                start = time.time()
                secure_delete(f)
                file_elapsed = time.time() - start
                bytes_deleted += filesize
                time_elapsed += file_elapsed
                rate = bytes_deleted / time_elapsed
                print(
                    "elapsed: {:.2f}s rate: {:.1f} MB/s overall rate: {:.1f} MB/s".format(
                        file_elapsed, filesize / 1048576 / file_elapsed, rate / 1048576
                    )
                )
            else:
                print("Not removing {}.".format(f))
Ejemplo n.º 3
0
def were_there_submissions_today(args, context=None):
    with context or app_context():
        something = (db.session.query(Source).filter(
            Source.last_updated > datetime.datetime.utcnow() -
            datetime.timedelta(hours=24)).count() > 0)
        count_file = os.path.join(args.data_root, "submissions_today.txt")
        open(count_file, "w").write(something and "1" or "0")
Ejemplo n.º 4
0
def list_disconnected_db_submissions(args):
    """
    List the IDs of Submission records whose files are missing.
    """
    with app_context():
        disconnected_submissions = find_disconnected_db_submissions(args.store_dir)
        if disconnected_submissions:
            print(
                'Run "manage.py delete-disconnected-db-submissions" to delete these records.',
                file=sys.stderr,
            )
        for s in disconnected_submissions:
            print(s.id)
Ejemplo n.º 5
0
def check_for_disconnected_db_submissions(args):
    """
    Check for Submission records whose files are missing.
    """
    with app_context():
        disconnected = find_disconnected_db_submissions(args.store_dir)
        if disconnected:
            print(
                "There are submissions in the database with no corresponding files. "
                'Run "manage.py list-disconnected-db-submissions" for details.'
            )
        else:
            print("No problems were found. All submissions' files are present.")
Ejemplo n.º 6
0
def list_disconnected_fs_submissions(args):
    """
    List files without a corresponding Submission or Reply record in the database.
    """
    with app_context():
        disconnected_files = find_disconnected_fs_submissions(args.store_dir)
        if disconnected_files:
            print(
                'Run "manage.py delete-disconnected-fs-submissions" to delete these files.',
                file=sys.stderr,
            )
        for f in disconnected_files:
            print(f)
Ejemplo n.º 7
0
def check_for_disconnected_fs_submissions(args):
    """
    Check for files without a corresponding Submission or Reply record in the database.
    """
    with app_context():
        disconnected = find_disconnected_fs_submissions(args.store_dir)
        if disconnected:
            print(
                "There are files in the submission area with no corresponding records in the "
                'database. Run "manage.py list-disconnected-fs-submissions" for details.'
            )
        else:
            print("No unexpected files were found in the store.")
Ejemplo n.º 8
0
def delete_disconnected_db_submissions(args):
    """
    Delete Submission records whose files are missing.
    """
    with app_context():
        disconnected_submissions = find_disconnected_db_submissions(args.store_dir)
        ids = [s.id for s in disconnected_submissions]

        remove = args.force
        if not args.force:
            remove = input("Enter 'y' to delete all submissions missing files: ") == "y"
        if remove:
            print("Removing submission IDs {}...".format(ids))
            db.session.query(Submission).filter(Submission.id.in_(ids)).delete(
                synchronize_session="fetch"
            )
            db.session.commit()
        else:
            print("Not removing disconnected submissions in database.")
Ejemplo n.º 9
0
def reset(args: argparse.Namespace,
          context: Optional[AppContext] = None) -> int:
    """Clears the SecureDrop development applications' state, restoring them to
    the way they were immediately after running `setup_dev.sh`. This command:
    1. Erases the development sqlite database file.
    2. Regenerates the database.
    3. Erases stored submissions and replies from the store dir.
    """
    # Erase the development db file
    if not hasattr(config, "DATABASE_FILE"):
        raise Exception("./manage.py doesn't know how to clear the db "
                        "if the backend is not sqlite")

    # we need to save some data about the old DB file so we can recreate it
    # with the same state
    try:
        stat_res = os.stat(config.DATABASE_FILE)
        uid = stat_res.st_uid
        gid = stat_res.st_gid
    except OSError:
        uid = os.getuid()
        gid = os.getgid()

    try:
        os.remove(config.DATABASE_FILE)
    except OSError:
        pass

    # Regenerate the database
    # 1. Create it
    subprocess.check_call(["sqlite3", config.DATABASE_FILE, ".databases"])
    # 2. Set permissions on it
    os.chown(config.DATABASE_FILE, uid, gid)
    os.chmod(config.DATABASE_FILE, 0o0640)

    if os.environ.get("SECUREDROP_ENV") == "dev":
        # 3. Create the DB from the metadata directly when in 'dev' so
        # developers can test application changes without first writing
        # alembic migration.
        with context or app_context():
            db.create_all()
    else:
        # We have to override the hardcoded .ini file because during testing
        # the value in the .ini doesn't exist.
        ini_dir = os.path.dirname(
            getattr(config, "TEST_ALEMBIC_INI", "alembic.ini"))

        # 3. Migrate it to 'head'
        # nosemgrep: python.lang.security.audit.subprocess-shell-true.subprocess-shell-true
        subprocess.check_call("cd {} && alembic upgrade head".format(ini_dir),
                              shell=True)  # nosec

    # Clear submission/reply storage
    try:
        os.stat(args.store_dir)
    except OSError:
        pass
    else:
        for source_dir in os.listdir(args.store_dir):
            try:
                # Each entry in STORE_DIR is a directory corresponding
                # to a source
                shutil.rmtree(os.path.join(args.store_dir, source_dir))
            except OSError:
                pass
    return 0
Ejemplo n.º 10
0
def _add_user(is_admin: bool = False,
              context: Optional[AppContext] = None) -> int:
    with context or app_context():
        username = _get_username()
        first_name = _get_first_name()
        last_name = _get_last_name()

        print("Note: Passwords are now autogenerated.")
        password = PassphraseGenerator.get_default().generate_passphrase()
        print("This user's password is: {}".format(password))

        is_hotp = _get_yubikey_usage()
        otp_secret = None
        if is_hotp:
            while True:
                otp_secret = obtain_input(
                    "Please configure this user's YubiKey and enter the "
                    "secret: ")
                if otp_secret:
                    tmp_str = otp_secret.replace(" ", "")
                    if len(tmp_str) != 40:
                        print("The length of the secret is not correct. "
                              "Expected 40 characters, but received {0}. "
                              "Try again.".format(len(tmp_str)))
                        continue
                if otp_secret:
                    break

        try:
            user = Journalist(
                username=username,
                first_name=first_name,
                last_name=last_name,
                password=password,
                is_admin=is_admin,
                otp_secret=otp_secret,
            )
            db.session.add(user)
            db.session.commit()
        except Exception as exc:
            db.session.rollback()
            if "UNIQUE constraint failed: journalists.username" in str(exc):
                print("ERROR: That username is already taken!")
            else:
                exc_type, exc_value, exc_traceback = sys.exc_info()
                print(
                    repr(
                        traceback.format_exception(exc_type, exc_value,
                                                   exc_traceback)))
            return 1
        else:
            print('User "{}" successfully added'.format(username))
            if not otp_secret:
                # Print the QR code for FreeOTP
                print("\nScan the QR code below with FreeOTP:\n")
                uri = user.totp.provisioning_uri(username,
                                                 issuer_name="SecureDrop")
                qr = qrcode.QRCode()
                qr.add_data(uri)
                qr.print_ascii(tty=sys.stdout.isatty())
                print("\nIf the barcode does not render correctly, try "
                      "changing your terminal's font (Monospace for Linux, "
                      "Menlo for OS X). If you are using iTerm on Mac OS X, "
                      'you will need to change the "Non-ASCII Font", which '
                      "is your profile's Text settings.\n\nCan't scan the "
                      "barcode? Enter following shared secret manually:"
                      "\n{}\n".format(user.formatted_otp_secret))
        return 0