示例#1
0
def test_change_nickname():
    """Change the nickname and press cancel."""
    # Given:
    form = EditController()
    form.start()
    # When we edit the nickname:
    form.ui.lineedit_nickname.setText("junk")
    cancel_button = form.ui.edit_buttons.button(form.ui.edit_buttons.Cancel)
    QTest.mouseClick(cancel_button, Qt.LeftButton)
    # Then dirty-flag is set:
    assert form.dirty_flag
示例#2
0
def test_change_nickname():
    """Change the nickname and press cancel."""
    # Given:
    form = EditController()
    form.start()
    # When we edit the nickname:
    form.ui.lineedit_nickname.setText("junk")
    cancel_button = form.ui.edit_buttons.button(form.ui.edit_buttons.Cancel)
    QTest.mouseClick(cancel_button, Qt.LeftButton)
    # Then dirty-flag is set:
    assert form.dirty_flag
示例#3
0
def test_do_nothing():
    """Just create the form and press ok."""
    # Given:
    form = EditController()
    form.start()
    # When we do nothing:
    ok_button = form.ui.edit_buttons.button(form.ui.edit_buttons.Ok)
    QTest.mouseClick(ok_button, Qt.LeftButton)
    # Then check defaults and form returns 1 because we ok'ed:
    assert not form.dirty_flag
    assert not form.update_flag
    assert form.result() == 1
示例#4
0
def test_do_nothing():
    """Just create the form and press ok."""
    # Given:
    form = EditController()
    form.start()
    # When we do nothing:
    ok_button = form.ui.edit_buttons.button(form.ui.edit_buttons.Ok)
    QTest.mouseClick(ok_button, Qt.LeftButton)
    # Then check defaults and form returns 1 because we ok'ed:
    assert not form.dirty_flag
    assert not form.update_flag
    assert form.result() == 1
示例#5
0
def test_base_and_special_chars():
    """Verify that base == 64 implies special-chars."""
    # Given:
    form = EditController()
    form.start()
    # When we set the base to 64:
    form.ui.combobox_base.setCurrentText("64")
    # Then we must have special chars:
    assert form.ui.checkbox_special_char.isChecked() == True
    # When we unset special chars:
    form.ui.checkbox_special_char.setChecked(False)
    # Then the base must revert to 32:
    assert form.ui.combobox_base.currentText() == "32"
示例#6
0
def test_toggle_special_chars():
    """Toggle the special-chars checkbox and press cancel."""
    # Given:
    form = EditController()
    form.start()
    # When we toggle the special-chars checkbox:
    form.ui.checkbox_special_char.setChecked(True)
    cancel_button = form.ui.edit_buttons.button(form.ui.edit_buttons.Cancel)
    QTest.mouseClick(cancel_button, Qt.LeftButton)
    # Then dirty-flag is set and form returns 0 b/c we cancelled:
    assert form.dirty_flag
    assert not form.update_flag
    assert form.result() == 0
示例#7
0
def test_base_and_special_chars():
    """Verify that base == 64 implies special-chars."""
    # Given:
    form = EditController()
    form.start()
    # When we set the base to 64:
    form.ui.combobox_base.setCurrentText("64")
    # Then we must have special chars:
    assert form.ui.checkbox_special_char.isChecked() == True
    # When we unset special chars:
    form.ui.checkbox_special_char.setChecked(False)
    # Then the base must revert to 32:
    assert form.ui.combobox_base.currentText() == "32"
示例#8
0
def test_toggle_special_chars():
    """Toggle the special-chars checkbox and press cancel."""
    # Given:
    form = EditController()
    form.start()
    # When we toggle the special-chars checkbox:
    form.ui.checkbox_special_char.setChecked(True)
    cancel_button = form.ui.edit_buttons.button(form.ui.edit_buttons.Cancel)
    QTest.mouseClick(cancel_button, Qt.LeftButton)
    # Then dirty-flag is set and form returns 0 b/c we cancelled:
    assert form.dirty_flag
    assert not form.update_flag
    assert form.result() == 0
示例#9
0
def test_update():
    """Update an existing password."""
    # Given:
    nick, user, host, iteration, hint = "nick", "user", "host.com", 3, "hint"
    orig_password = Password(nick, user, host, iteration=iteration, hint=hint)
    new_nick, new_user = "******", "new_user"
    form = EditController()
    form.start()
    form.populate_form_from_password(orig_password)
    # Skip the "Are you sure?" messagebox:
    form.ui.edit_buttons.accepted.disconnect(form.confirm_accept)
    form.ui.edit_buttons.accepted.connect(form.accept)
    # When we update the original password:
    form.ui.lineedit_nickname.setText(new_nick)
    form.ui.lineedit_username.setText(new_user)
    ok_button = form.ui.edit_buttons.button(form.ui.edit_buttons.Ok)
    QTest.mouseClick(ok_button, Qt.LeftButton)
    password = form.create_password_from_form()
    # Then we get those updates back:
    assert form.dirty_flag
    assert form.update_flag
    assert form.result() == 1
    assert password == Password(new_nick,
                                new_user,
                                host,
                                iteration=iteration,
                                hint=hint)
示例#10
0
def test_create():
    """Create a new password."""
    # Given:
    form = EditController()
    form.start()
    nick, user, host, iteration, hint = "nick", "user", "host.com", 3, "hint"
    # When we create a new pw:
    form.ui.lineedit_nickname.setText(nick)
    form.ui.lineedit_username.setText(user)
    form.ui.lineedit_hostname.setText(host)
    form.ui.spinbox_iteration.setValue(iteration)
    form.ui.lineedit_hint.setText(hint)
    ok_button = form.ui.edit_buttons.button(form.ui.edit_buttons.Ok)
    QTest.mouseClick(ok_button, Qt.LeftButton)
    password = form.create_password_from_form()
    # Then we get that new pw back:
    assert form.dirty_flag
    assert not form.update_flag
    assert form.result() == 1
    assert password == Password(nickname=nick, username=user, hostname=host, iteration=iteration, hint=hint)
示例#11
0
def test_update():
    """Update an existing password."""
    # Given:
    nick, user, host, iteration, hint = "nick", "user", "host.com", 3, "hint"
    orig_password = Password(nick, user, host, iteration=iteration, hint=hint)
    new_nick, new_user = "******", "new_user"
    form = EditController()
    form.start()
    form.populate_form_from_password(orig_password)
    # Skip the "Are you sure?" messagebox:
    form.ui.edit_buttons.accepted.disconnect(form.confirm_accept)
    form.ui.edit_buttons.accepted.connect(form.accept)
    # When we update the original password:
    form.ui.lineedit_nickname.setText(new_nick)
    form.ui.lineedit_username.setText(new_user)
    ok_button = form.ui.edit_buttons.button(form.ui.edit_buttons.Ok)
    QTest.mouseClick(ok_button, Qt.LeftButton)
    password = form.create_password_from_form()
    # Then we get those updates back:
    assert form.dirty_flag
    assert form.update_flag
    assert form.result() == 1
    assert password == Password(new_nick, new_user, host, iteration=iteration, hint=hint)
示例#12
0
def test_create():
    """Create a new password."""
    # Given:
    form = EditController()
    form.start()
    nick, user, host, iteration, hint = "nick", "user", "host.com", 3, "hint"
    # When we create a new pw:
    form.ui.lineedit_nickname.setText(nick)
    form.ui.lineedit_username.setText(user)
    form.ui.lineedit_hostname.setText(host)
    form.ui.spinbox_iteration.setValue(iteration)
    form.ui.lineedit_hint.setText(hint)
    ok_button = form.ui.edit_buttons.button(form.ui.edit_buttons.Ok)
    QTest.mouseClick(ok_button, Qt.LeftButton)
    password = form.create_password_from_form()
    # Then we get that new pw back:
    assert form.dirty_flag
    assert not form.update_flag
    assert form.result() == 1
    assert password == Password(nickname=nick,
                                username=user,
                                hostname=host,
                                iteration=iteration,
                                hint=hint)
示例#13
0
 def __init__(self, app):
     super().__init__()
     self.app = app
     self.ui = Ui_main_form()
     self.edit_form = EditController()
     self.pass_db, self.passwords_dic = None, None
示例#14
0
class MainController(QtWidgets.QDialog):
    """Handle communication between the main Qt form and the database."""
    def __init__(self, app):
        super().__init__()
        self.app = app
        self.ui = Ui_main_form()
        self.edit_form = EditController()
        self.pass_db, self.passwords_dic = None, None

    def start(self, pass_db, pass_dic):
        """Real initialization: ui, passwords-list, connect callbacks"""
        self.ui.setupUi(self)
        self.edit_form.start()
        self._connect_callbacks()
        self.pass_db, self.passwords_dic = pass_db, pass_dic
        self._populate_pw_nicknames_list()

    @staticmethod
    def create(app, pass_db=None, pass_dic=None, db_path=None):
        """We're given either (in prod) a db_path that we use to get a pass_db
        and pass_dic, or (in test) a pre-built pass_db and pass_dic."""
        main_form = MainController(app)
        if db_path is not None:
            pass_db, pass_dic = main_form._get_data(db_path)
        main_form.start(pass_db, pass_dic)
        return main_form

    def _get_data(self, db_path):
        """Create communications methods for the back-end get_data and call it."""
        def ask_to_create_new():
            """Ask user if we want to create a new password db."""
            msg_box = QtWidgets.QMessageBox()
            msg_box.setFont(self.font())
            msg_box.setText("Password database doesn't already exist.")
            msg_box.setInformativeText("Create a new one?")
            msg_box.setStandardButtons(QtWidgets.QMessageBox.Yes
                                       | QtWidgets.QMessageBox.No)
            response = msg_box.exec_()
            return response == QtWidgets.QMessageBox.Yes

        def error_getting_db():
            """Notify user of error getting password database."""
            msg_box = QtWidgets.QMessageBox()
            msg_box.setFont(self.font())
            msg_box.setText("Error opening passwords db.  Exiting.")
            msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok)
            msg_box.exec_()

        return PasswordDB.get_data(ask_to_create_new, error_getting_db,
                                   db_path)

    def _populate_pw_nicknames_list(self):
        """Populate the main drop-down containing all password nicknames.
        According to how many items there are, decide which buttons
        on the form need to be enabled.
        """
        def _set_widgets_enabled(state):
            """Enable or disable widgets depending on whether we have data yet."""
            for widget in [
                    self.ui.combobox_password_nicknames,
                    self.ui.lineedit_enter_proto, self.ui.button_get,
                    self.ui.button_hint, self.ui.button_update,
                    self.ui.button_delete
            ]:
                widget.setEnabled(state)

        nicknames = sorted(self.passwords_dic.keys())
        self.ui.combobox_password_nicknames.clear()
        if len(nicknames) > 1:  # add header and nicknames
            self.ui.combobox_password_nicknames.addItems(
                [_NICKNAMES_LIST_HEADER])
        self.ui.combobox_password_nicknames.addItems(nicknames)
        if nicknames:  # have passwords, can do stuff with them
            _set_widgets_enabled(True)
            self.ui.combobox_password_nicknames.setFocus()
        else:  # no passwords yet, can only create new
            _set_widgets_enabled(False)
            self.ui.button_new.setFocus()

    def _connect_callbacks(self):
        """Connect ui events to callbacks."""
        self.ui.lineedit_enter_proto.returnPressed.connect(self.get_password)
        self.ui.checkbox_show_password.stateChanged.connect(
            self.toggle_show_password)
        self.ui.combobox_password_nicknames.currentIndexChanged.connect(
            self._clear_both)
        self.ui.button_get.clicked.connect(self.get_password)
        self.ui.button_hint.clicked.connect(self.get_hint)
        self.ui.button_new.clicked.connect(self.create_or_update_password)
        self.ui.button_update.clicked.connect(self.create_or_update_password)
        self.ui.button_delete.clicked.connect(self.delete_password)

    def _get_selected(self):
        """Figure out which password-nick they selected."""
        selected_text = str(self.ui.combobox_password_nicknames.currentText())
        if selected_text != _NICKNAMES_LIST_HEADER:
            return (self.ui.combobox_password_nicknames.currentIndex(),
                    selected_text)
        return (0, None)

    # basic interactions with the main window:
    def _display_message(self, line_1, line_2):
        """Display the given message, lines 1 and 2."""
        self.ui.label_resp_header.setText(line_1)
        self.ui.label_resp_body.setText(line_2)

    def _display_error(self, error_message):
        """Display the given error."""
        self._display_message("<font color=red>ERROR</font>",
                              "<font color=red>" + error_message + "</font>")

    def _clear_proto_passwords(self):
        """Clear the line where user enters proto-password."""
        self.ui.lineedit_enter_proto.setText("")

    def _clear_display(self):
        """Clear the main display line."""
        self._display_message('<font size="48">&nbsp;</font>', "")

    def _clear_both(self):
        """Clear both the user-entry line and the main display line."""
        self._clear_proto_passwords()
        self._clear_display()

    def get_password(self):
        """Get password for a particular selection."""
        self._clear_display()
        # get the selection:
        _, selection = self._get_selected()
        if not selection:
            self._display_error(_SELECTION_ERROR)
            return
        proto_pw_1 = str(self.ui.lineedit_enter_proto.text())
        password = self.passwords_dic[selection].calculate_password(proto_pw_1)
        self._display_message("Password copied to clipboard:", password)
        self.app.clipboard().setText(password)
        self.ui.combobox_password_nicknames.setFocus()

    def get_hint(self):
        """Look up the stored hint and display it."""
        # Get the selection:
        _, selection = self._get_selected()
        if not selection:
            self._display_error(_SELECTION_ERROR)
            return
        # Get the  hint, display it, and go to entry line-edit
        self._display_message("Hint is:", self.passwords_dic[selection].hint)
        self.ui.lineedit_enter_proto.setFocus()
        self.ui.lineedit_enter_proto.selectAll()

    def create_or_update_password(self):
        """
        Use same form either to create a new password or to update an
        existing password.
        """
        def get_password_from_user(is_update):
            """Create an entry form, validate user input, and return a Password object."""
            # If we're updating then populate the entry form with existing data to update:
            _, orig_nickname = self._get_selected()
            orig_pass = self.passwords_dic.get(orig_nickname, None)
            if is_update:
                if orig_pass:
                    self.edit_form.populate_form_from_password(orig_pass)
                else:
                    self._display_error(_SELECTION_ERROR)
                    return None, None
            else:
                self.edit_form.clear()

            # We'll keep showing the create-new-password window until we get good data.
            # (Unless the user cancels, in which case of course quit.)
            bad_data_entered = True  # Human-entered data is bad until we check it
            while bad_data_entered:
                # If the user cancels then quit.
                self.edit_form.ui.lineedit_nickname.setFocus()
                edit_return = self.edit_form.exec_()
                if not edit_return:
                    return None, None
                # Check for valid data: can't have either a blank nickname or one that already exists.
                nickname = str(self.edit_form.ui.lineedit_nickname.text())
                nickname_is_blank = nickname == ''
                nickname_already_exists = nickname in self.passwords_dic
                bad_data_entered = nickname_is_blank or (
                    nickname_already_exists and not is_update)
                if bad_data_entered:
                    msg_box = QtWidgets.QMessageBox()
                    if nickname_is_blank:
                        msg_box.setText(
                            'Nickname cannot be blank. Please try again.')
                    if nickname_already_exists and not is_update:
                        msg_box.setText(
                            nickname +
                            ' already exists in password database. Please try again.'
                        )
                    msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok)
                    msg_box.setFont(self.font())
                    msg_box.exec_()
            return orig_nickname, self.edit_form.create_password_from_form()

        is_update = self.sender() == self.ui.button_update
        orig_nickname, password = get_password_from_user(is_update)
        if password is None:
            return
        # Either update the data-store or create a new password.
        if is_update:
            self.pass_db.update_old_password(orig_nickname, password)
            del self.passwords_dic[orig_nickname]
        else:
            self.pass_db.create_new_password(password)
        self.passwords_dic[password.nickname] = password
        self._populate_pw_nicknames_list()
        self._clear_both()
        return

    def delete_password(self, confirmed=False):
        """Respond to a request to delete a particular password.

        confirmed (default False) is a hack to allow testing without a confirmation messagebox.
        """
        def confirm_deletion(selection):
            """Confirm whether we want to delete."""
            msg_box = QtWidgets.QMessageBox()
            msg_box.setText('About to delete password "' + selection + '".')
            msg_box.setInformativeText('Are you sure?')
            msg_box.setStandardButtons(QtWidgets.QMessageBox.Yes
                                       | QtWidgets.QMessageBox.No)
            msg_box.setFont(self.font())
            response = msg_box.exec_()
            return response == QtWidgets.QMessageBox.Yes

        # Clear the display:
        self._clear_both()
        # Get the selection:
        _, selection = self._get_selected()
        if not selection:
            self._display_error(_SELECTION_ERROR)
            return
        # Confirm and delete:
        if confirmed or confirm_deletion(selection):
            self.pass_db.delete_password(selection)
            del self.passwords_dic[selection]
            self._populate_pw_nicknames_list()
            self._display_message("STATUS:", selection + " has been deleted.")

    def reject(self):
        """User is dismissing the form.  Close up shop before closing app."""
        self.app.clipboard().setText('')
        self.pass_db.close_db()
        super().reject()

    def toggle_show_password(self):
        """Toggle whether or to display the password as it's typed."""
        if self.ui.checkbox_show_password.isChecked():
            self.ui.lineedit_enter_proto.setEchoMode(
                QtWidgets.QLineEdit.Normal)
        else:
            self.ui.lineedit_enter_proto.setEchoMode(
                QtWidgets.QLineEdit.Password)