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)
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)
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
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()
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
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)
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
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
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()
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()
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 '
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)