Пример #1
0
    def new_journalist(self):
        # Make a diceware-like password
        pw = ' '.join([
            random_chars(3, nullable=False, chars=DICEWARE_SAFE_CHARS)
            for _ in range(7)
        ])
        journalist = Journalist(username=random_chars(random.randint(3, 32),
                                                      nullable=False),
                                password=pw,
                                is_admin=random_bool())
        if random_bool():
            # to add legacy passwords back in
            journalist.passphrase_hash = None
            journalist.pw_salt = random_chars(32,
                                              nullable=False).encode('utf-8')
            journalist.pw_hash = random_chars(64,
                                              nullable=False).encode('utf-8')

        journalist.is_admin = bool_or_none()

        journalist.is_totp = bool_or_none()
        journalist.hotp_counter = (random.randint(-1000, 1000)
                                   if random_bool() else None)
        journalist.created_on = random_datetime(nullable=True)
        journalist.last_access = random_datetime(nullable=True)

        db.session.add(journalist)
        db.session.flush()
        self.journalists.append(journalist.id)
Пример #2
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)
Пример #3
0
def add_test_user(username: str, password: str, otp_secret: str, is_admin: bool = False,
                  first_name: str = "", last_name: str = "") -> Journalist:
    user = Journalist(username=username,
                      password=password,
                      is_admin=is_admin,
                      first_name=first_name,
                      last_name=last_name)
    user.otp_secret = otp_secret
    db.session.add(user)
    db.session.commit()
    print('Test user successfully added: '
          'username={}, password={}, otp_secret={}, is_admin={}'
          ''.format(username, password, otp_secret, is_admin))
    return user
Пример #4
0
def add_test_user(username, password, otp_secret, is_admin=False):
    try:
        user = Journalist(username=username,
                          password=password,
                          is_admin=is_admin)
        user.otp_secret = otp_secret
        db.session.add(user)
        db.session.commit()
        print('Test user successfully added: '
              'username={}, password={}, otp_secret={}, is_admin={}'
              ''.format(username, password, otp_secret, is_admin))
    except IntegrityError:
        print("Test user already added")
        db.session.rollback()
Пример #5
0
def add_journalist(
    username: str = "",
    is_admin: bool = False,
    first_name: str = "",
    last_name: str = "",
    progress: Optional[Tuple[int, int]] = None,
) -> Journalist:
    """
    Adds a single journalist account.
    """
    test_password = "******"
    test_otp_secret = "JHCOGO7VCER3EJ4L"

    if not username:
        username = current_app.crypto_util.display_id()

    journalist = Journalist(
        username=username,
        password=test_password,
        first_name=first_name,
        last_name=last_name,
        is_admin=is_admin,
    )
    journalist.otp_secret = test_otp_secret
    if random_bool():
        # to add legacy passwords back in
        journalist.passphrase_hash = None
        salt = random_chars(32).encode("utf-8")
        journalist.pw_salt = salt
        journalist.pw_hash = journalist._scrypt_hash(test_password, salt)

    db.session.add(journalist)
    attempt = JournalistLoginAttempt(journalist)
    attempt.timestamp = random_datetime(nullable=True)
    db.session.add(attempt)
    db.session.commit()

    print(
        "Created {}journalist{} (username={}, password={}, otp_secret={}, is_admin={})".format(
            "additional " if progress else "",
            " {}/{}".format(*progress) if progress else "",
            username,
            test_password,
            test_otp_secret,
            is_admin,
        )
    )
    return journalist
Пример #6
0
def register():
    form = RegistrationForm(meta={'csrf': False})
    if form.validate_on_submit():
        try:
            user = Journalist(name=form.username.data,
                              email=form.email.data,
                              surname=form.user_surname.data)
            app.logger.info(f'try register user "{form.username.data}" '
                            f'"{form.user_surname.data}" "{form.email.data}"')
            user.set_password(form.password.data)
            db.session.add(user)
            db.session.commit()

        except Exception as e:
            flash("Please enter a valid registration data")
            app.logger.error(f'registration failed: {e}')
        else:
            flash("You successfully register!")
            app.logger.info('registration successful')
    return render_template('register.html', title='Register', form=form)
Пример #7
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
Пример #8
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
Пример #9
0
def spawn_sd_servers(
    config_to_use: SecureDropConfig,
    journalist_app_setup_callback: Optional[Callable[[SecureDropConfig],
                                                     Any]] = None,
) -> Generator[SdServersFixtureResult, None, None]:
    """Spawn the source and journalist apps as separate processes with the supplied config.

    The journalist_app_setup_callback can be used to run a setup function within the journalist
    app's process right before the app starts (for setting up state needed by the test).
    """
    journalist_app_process = None
    source_app_process = None
    try:
        # Add a test journalist
        with get_database_session(database_uri=config_to_use.DATABASE_URI
                                  ) as db_session_for_sd_servers:
            journalist_password = "******"
            journalist_username = "******"
            journalist_otp_secret = "JHCOGO7VCER3EJ4L"
            journalist = Journalist(
                username=journalist_username,
                password=journalist_password,
                is_admin=True,
            )
            journalist.otp_secret = journalist_otp_secret
            db_session_for_sd_servers.add(journalist)
            db_session_for_sd_servers.commit()

        # Spawn the source and journalist web apps in separate processes
        source_port = _get_unused_port()
        journalist_port = _get_unused_port()

        # Start the server subprocesses using the "spawn" method instead of "fork".
        # This is needed for the config_to_use argument to work; if "fork" is used, the subprocess
        # will inherit all the globals from the parent process, which will include the Python
        # variables declared as "global" in the SD code base
        # (example: see _DesignationGenerator.get_default()).
        # This means the config_to_use will be ignored if these globals have already been
        # initialized (for example by tests running before the code here).
        mp_spawn_ctx = multiprocessing.get_context("spawn")

        source_app_process = mp_spawn_ctx.Process(  # type: ignore
            target=_start_source_server,
            args=(source_port, config_to_use))
        source_app_process.start()
        journalist_app_process = mp_spawn_ctx.Process(  # type: ignore
            target=_start_journalist_server,
            args=(journalist_port, config_to_use,
                  journalist_app_setup_callback),
        )
        journalist_app_process.start()
        source_app_base_url = f"http://127.0.0.1:{source_port}"
        journalist_app_base_url = f"http://127.0.0.1:{journalist_port}"

        # Sleep until the source and journalist web apps are up and running
        response_source_status_code = None
        response_journalist_status_code = None
        for _ in range(30):
            try:
                response_source = requests.get(source_app_base_url, timeout=1)
                response_source_status_code = response_source.status_code
                response_journalist = requests.get(journalist_app_base_url,
                                                   timeout=1)
                response_journalist_status_code = response_journalist.status_code
                break
            except requests.ConnectionError:
                time.sleep(0.25)
        assert response_source_status_code == 200
        assert response_journalist_status_code == 200

        # Ready for the tests
        yield SdServersFixtureResult(
            source_app_base_url=source_app_base_url,
            journalist_app_base_url=journalist_app_base_url,
            config_in_use=config_to_use,
            journalist_username=journalist_username,
            journalist_password=journalist_password,
            journalist_otp_secret=journalist_otp_secret,
        )

    # Clean everything up
    finally:
        if source_app_process:
            source_app_process.terminate()
            source_app_process.join()
        if journalist_app_process:
            journalist_app_process.terminate()
            journalist_app_process.join()
Пример #10
0
    def sd_servers(self):
        logging.info("Starting SecureDrop servers (session expiration = %s)",
                     self.session_expiration)

        # Patch the two-factor verification to avoid intermittent errors
        logging.info("Mocking models.Journalist.verify_token")
        with mock.patch("models.Journalist.verify_token", return_value=True):
            logging.info("Mocking source_app.main.get_entropy_estimate")
            with mock.patch("source_app.main.get_entropy_estimate",
                            return_value=8192):

                try:
                    signal.signal(signal.SIGUSR1,
                                  lambda _, s: traceback.print_stack(s))

                    source_port = self._unused_port()
                    journalist_port = self._unused_port()

                    self.source_location = "http://127.0.0.1:%d" % source_port
                    self.journalist_location = "http://127.0.0.1:%d" % journalist_port

                    self.source_app = source_app.create_app(config)
                    self.journalist_app = journalist_app.create_app(config)
                    self.journalist_app.config["WTF_CSRF_ENABLED"] = True

                    self.__context = self.journalist_app.app_context()
                    self.__context.push()

                    env.create_directories()
                    db.create_all()
                    self.gpg = env.init_gpg()

                    # Add our test user
                    try:
                        valid_password = "******"
                        user = Journalist(username="******",
                                          password=valid_password,
                                          is_admin=True)
                        user.otp_secret = "JHCOGO7VCER3EJ4L"
                        db.session.add(user)
                        db.session.commit()
                    except IntegrityError:
                        logging.error("Test user already added")
                        db.session.rollback()

                    # This user is required for our tests cases to login
                    self.admin_user = {
                        "name":
                        "journalist",
                        "password": ("correct horse battery staple"
                                     " profanity oil chewy"),
                        "secret":
                        "JHCOGO7VCER3EJ4L",
                    }

                    self.admin_user["totp"] = pyotp.TOTP(
                        self.admin_user["secret"])

                    def start_journalist_server(app):
                        app.run(port=journalist_port,
                                debug=True,
                                use_reloader=False,
                                threaded=True)

                    self.source_process = Process(
                        target=lambda: self.start_source_server(source_port))

                    self.journalist_process = Process(
                        target=lambda: start_journalist_server(self.
                                                               journalist_app))

                    self.source_process.start()
                    self.journalist_process.start()

                    for tick in range(30):
                        try:
                            requests.get(self.source_location, timeout=1)
                            requests.get(self.journalist_location, timeout=1)
                        except Exception:
                            time.sleep(0.25)
                        else:
                            break
                    yield
                finally:
                    try:
                        self.source_process.terminate()
                    except Exception as e:
                        logging.error("Error stopping source app: %s", e)

                    try:
                        self.journalist_process.terminate()
                    except Exception as e:
                        logging.error("Error stopping source app: %s", e)

                    env.teardown()
                    self.__context.pop()
Пример #11
0
from app import db
from models import Journalist, News
from tests.test_data import journalist1, journalist2

filename = os.path.splitext(__file__)[0]
logging.basicConfig(format='%(asctime)s,%(msecs)d '
                    '%(name)s %(levelname)s %(message)s',
                    datefmt='%H:%M:%S',
                    filemode='w',
                    level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.info("Start create DB data")

j1 = Journalist(id=1,
                name='Ann',
                surname='Smith',
                email='*****@*****.**',
                password_hash=hash)
j1.set_password(journalist1['password'])
j2 = Journalist(id=2, name='Bob', surname='Bin', email='*****@*****.**')
j2.set_password(journalist2['password'])
n1 = News(id=1,
          title='fine weather',
          text='Weather is driven by air pressure, '
          'temperature, '
          'and moisture differences between '
          'one place and another. '
          'These differences can occur due to the Sun\'s angle '
          'at any particular spot, '
          'which varies with latitude. '
          'The strong temperature contrast between polar and '
Пример #12
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 (binascii.Error, TypeError) as e:
                if "Non-hexadecimal digit found" in str(e):
                    flash(
                        gettext(
                            "Invalid HOTP secret format: "
                            "please only submit letters A-F and numbers 0-9."),
                        "error",
                    )
                else:
                    flash(
                        gettext("An unexpected error occurred! "
                                "Please inform your admin."),
                        "error",
                    )
                form_valid = False
            except InvalidUsernameException as e:
                form_valid = False
                # Translators: Here, "{message}" explains the problem with the username.
                flash(
                    gettext("Invalid username: {message}").format(message=e),
                    "error")
            except IntegrityError as e:
                db.session.rollback()
                form_valid = False
                if "UNIQUE constraint failed: journalists.username" in str(e):
                    flash(
                        gettext('Username "{username}" already taken.').format(
                            username=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)