Ejemplo n.º 1
0
class MainWidget(TritonWidget):
    def __init__(self, base):
        TritonWidget.__init__(self, base)
        self.addOTP = None
        self.closeEvent = self.widgetDeleted

        self.setWindowTitle('TritonAuth')
        self.setBackgroundColor(self, Qt.white)

        self.menu = QMenuBar()
        self.addMenu = self.menu.addMenu('Add')
        self.authAction = QAction('Authenticator', self)
        self.authAction.triggered.connect(self.openAddOTP)
        self.steamAction = QAction('Steam', self)
        self.steamAction.triggered.connect(self.openAddSteam)

        self.addMenu.addAction(self.authAction)
        self.addMenu.addAction(self.steamAction)

        self.sortMenu = self.menu.addMenu('Sort')
        self.nameAction = QAction('Sort by name', self)
        self.nameAction.triggered.connect(self.sortByName)

        self.sortMenu.addAction(self.nameAction)

        self.exportMenu = self.menu.addMenu('Export')
        self.andOTPAction = QAction('Export to andOTP', self)
        self.andOTPAction.triggered.connect(self.exportToAndOTP)

        self.exportMenu.addAction(self.andOTPAction)

        self.widget = QWidget()
        self.widget.setContentsMargins(10, 10, 10, 10)

        self.scrollArea = QScrollArea()
        self.scrollArea.setFixedSize(400, 495)
        self.scrollArea.setWidgetResizable(True)
        self.scrollWidget = QWidget()
        self.scrollLayout = QVBoxLayout(self.scrollWidget)
        self.scrollLayout.setAlignment(Qt.AlignTop)

        self.createAccounts()

        self.scrollArea.setWidget(self.scrollWidget)

        self.widgetLayout = QVBoxLayout(self.widget)
        self.widgetLayout.addWidget(self.scrollArea)

        self.boxLayout = QVBoxLayout(self)
        self.boxLayout.setContentsMargins(0, 5, 0, 0)
        self.boxLayout.addWidget(self.menu)
        self.boxLayout.addWidget(self.widget)

        self.setFixedSize(self.sizeHint())
        self.center()
        self.show()

    def keyPressEvent(self, event):
        if type(event) != QKeyEvent:
            return

        letter = event.text().strip().lower()

        for i in range(self.scrollLayout.count()):
            widget = self.scrollLayout.itemAt(i).widget()

            if widget is not None and widget.name[0].lower() == letter:
                self.scrollArea.verticalScrollBar().setValue(
                    widget.geometry().top())
                return

    def widgetDeleted(self, arg):
        self.closeAddOTP()

    def closeAddOTP(self):
        if self.addOTP:
            self.addOTP.close()
            self.addOTP = None

    def addAccount(self, account):
        entry = EntryWidget(self.base, account)
        self.scrollLayout.addWidget(entry)

    def deleteAccount(self, account):
        for i in range(self.scrollLayout.count()):
            widget = self.scrollLayout.itemAt(i).widget()

            if widget.account == account:
                widget.close()

    def clearAccounts(self):
        for i in range(self.scrollLayout.count()):
            self.scrollLayout.itemAt(i).widget().close()

    def createAccounts(self):
        for account in self.base.getAccounts():
            self.addAccount(account)

    def openAddOTP(self):
        self.closeAddOTP()
        self.addOTP = AddOTPWidget(self.base)

    def openAddSteam(self):
        self.closeAddOTP()
        self.addOTP = AddSteamWidget(self.base)

    def sortByName(self):
        self.base.sortAccountsByName()
        self.clearAccounts()
        self.createAccounts()

    def exportToAndOTP(self):
        accounts = []

        for account in self.base.getAccounts():
            type = account['type']

            if type == Globals.OTPAuth:
                accounts.append({
                    'secret': account['key'],
                    'digits': 6,
                    'period': 30,
                    'label': account['name'],
                    'type': 'TOTP',
                    'algorithm': 'SHA1',
                    'thumbnail': 'Default',
                    'last_used': 0,
                    'tags': []
                })
            elif type == Globals.SteamAuth:
                accounts.append({
                    'secret':
                    base64.b32encode(base64.b64decode(
                        account['sharedSecret'])).decode('utf-8'),
                    'digits':
                    5,
                    'period':
                    30,
                    'label':
                    account['name'],
                    'type':
                    'STEAM',
                    'algorithm':
                    'SHA1',
                    'thumbnail':
                    'Default',
                    'last_used':
                    0,
                    'tags': []
                })

        accounts = json.dumps(accounts)
        filename, _ = QFileDialog.getSaveFileName(
            self, 'Export to andOTP JSON file', '', 'All Files (*)')

        if filename:
            with open(filename, 'w') as file:
                file.write(accounts)
class OrderableListWidget(QScrollArea):
    """All available items in this list"""
    _item_list: list[OrderableListItem]
    """This lists actual widget"""
    _widget: QWidget
    """The widgets layout"""
    _layout: QLayout
    """Decides which way this list is ordered; 1 for ascending, -1 for descending"""
    _order_factor: int

    def __init__(self, order_asc=True, orientation_horizontal=False):
        """Init gui
        :type order_asc: bool
        :param order_asc: Whether to order the list ascending
        :type orientation_horizontal: bool
        :param orientation_horizontal: Should the list orientation be horizontal?
        """
        super().__init__()
        if order_asc:
            self._order_factor = 1
        else:
            self._order_factor = -1
        self._widget = QWidget()
        self.setWidget(self._widget)
        self.setWidgetResizable(True)
        # Set layout
        if orientation_horizontal:
            self._layout = QHBoxLayout()
        else:
            self._layout = QVBoxLayout()
        self._widget.setLayout(self._layout)
        self._layout.setAlignment(Qt.AlignTop)
        self._item_list = []

    def _get_order(self, list_item_a, list_item_b):
        """Defines this lists widget order
        :type list_item_a: OrderableListItem
        :param list_item_a: The first item to compare
        :type list_item_b: OrderableListItem
        :param list_item_b: The second item to compare
        :returns -1|0|1: list_item_a is: before, same, after list_item_b"""
        str_a: str = list_item_a.get_order_string()
        str_b: str = list_item_b.get_order_string()

        if str_a == str_b:
            return 0
        elif str_a < str_b:
            return -1 * self._order_factor
        else:
            return 1 * self._order_factor

    def add(self, list_item):
        """Add a new item to the list
        :type list_item: OrderableListItem
        :param list_item: The item to add
        """
        # Subscribe to changes
        list_item.subscribe(OrderableListItem.DELETED, self._item_deleted)
        list_item.subscribe(OrderableListItem.UPDATED, self._item_updated)
        # Make sure to add the item only once
        if list_item not in self._item_list:
            list_item_inserted = False
            self._item_list.append(list_item)

            # Walk all existing items
            for i in range(self._layout.count()):
                existing_item: OrderableListItem = self._layout.itemAt(i).widget()

                if 1 == self._get_order(existing_item, list_item):
                    self._layout.insertWidget(i, list_item)
                    list_item_inserted = True
                    break
            if not list_item_inserted:
                self._layout.addWidget(list_item)

    def _item_deleted(self, item):
        """Delete an item from the list
        :type item: OrderableListItem
        :param item: The item to delete
        """
        # See if the item exists in this list
        try:
            i: int = self._item_list.index(item)
        except ValueError:
            return
        # Delete the item
        self._item_list.pop(i)

    def _item_updated(self, item):
        """Update the list with the items new information
        :type item: OrderableListItem
        :param item: The item that was updated
        """
        pass