Exemplo n.º 1
0
class EmployeeList(object):
    def __init__(self, employees):
        self._employees = employees
        self._employees.add_change_listener(self)
        self._list = JList(preferredSize=(200, 200), name='employee_list')
        self._populate_list()

    def _populate_list(self):
        self._list.setListData(self._employee_names())

    def _employee_names(self):
        return [e.name for e in self._employees.all()]

    def add_selection_listener(self, listener):
        self._list.addListSelectionListener(listener)

    def selected_employee(self):
        return self._employees.all()[self._list.getSelectedIndex()]

    def employee_added(self, employee):
        self._populate_list()
        self._list.setSelectedValue(employee.name, True)

    def adding_employee_failed(self, error):
        pass

    def clear_selection(self):
        self._list.clearSelection()

    @property
    def widget(self):
        return self._list
Exemplo n.º 2
0
class EmployeeList(object):

    def __init__(self, employees):
        self._employees = employees
        self._employees.add_change_listener(self)
        self._list = JList(preferredSize=(200, 200), name='employee_list')
        self._populate_list()

    def _populate_list(self):
        self._list.setListData(self._employee_names())

    def _employee_names(self):
        return [e.name for e in self._employees.all()]

    def add_selection_listener(self, listener):
        self._list.addListSelectionListener(listener)

    def selected_employee(self):
        return self._employees.all()[self._list.getSelectedIndex()]

    def employee_added(self, employee):
        self._populate_list()
        self._list.setSelectedValue(employee.name, True)

    def adding_employee_failed(self, error):
        pass

    def clear_selection(self):
        self._list.clearSelection()

    @property
    def widget(self):
        return (self._list)
        return JScrollPane(self._list)
Exemplo n.º 3
0
class EmployeeList(object):
    def __init__(self, employees):
        self._employees = employees
        self._employees.add_change_listener(self)
        self._list = JList(preferredSize=(200, 200), name="employee_list")
        self._populate_list()

    def _populate_list(self):
        self._list.setListData(self._employee_names())

    def _employee_names(self):
        return [e.name for e in self._employees.all()]

    def add_selection_listener(self, listener):
        self._list.addListSelectionListener(listener)

    def selected_employee(self):
        return self._employees.all()[self._list.getSelectedIndex()]

    def employee_added(self, employee):
        self._populate_list()
        self._list.setSelectedValue(employee.name, True)

    def adding_employee_failed(self, error):
        pass

    def clear_selection(self):
        self._list.clearSelection()

    @property
    def widget(self):
        # BUGZ: EmployeeList is not scrollable. Adding more employees than
        # fits in the visible area makes some of them unreachable via UI.
        # Uncomment the following line to fix the bug:
        # return JScrollPane(self._list)
        return self._list
Exemplo n.º 4
0
class EmployeeList(object):
    def __init__(self, employees):
        self._employees = employees
        self._employees.add_change_listener(self)
        self._list = JList(preferredSize=(200, 200), name='employee_list')
        self._populate_list()

    def _populate_list(self):
        self._list.setListData(self._employee_names())

    def _employee_names(self):
        return [e.name for e in self._employees.all()]

    def add_selection_listener(self, listener):
        self._list.addListSelectionListener(listener)

    def selected_employee(self):
        return self._employees.all()[self._list.getSelectedIndex()]

    def employee_added(self, employee):
        self._populate_list()
        self._list.setSelectedValue(employee.name, True)

    def adding_employee_failed(self, error):
        pass

    def clear_selection(self):
        self._list.clearSelection()

    @property
    def widget(self):
        # BUGZ: EmployeeList is not scrollable. Adding more employees than
        # fits in the visible area makes some of them unreachable via UI.
        # Uncomment the following line to fix the bug:
        # return JScrollPane(self._list)
        return self._list
Exemplo n.º 5
0
class ProtoBufEditorTab(burp.IMessageEditorTab):
    """Tab in interceptor/repeater for editing protobuf message.
    Decodes them to JSON and back.
    The message type is attached to this object.
    """

    def __init__(self, extension, controller, editable, callbacks):

        self._callbacks = callbacks
        self._extension = extension
        self._callbacks = extension.callbacks
        self._helpers = extension.helpers

        self._controller = controller

        self._text_editor = self._callbacks.createTextEditor()
        self._text_editor.setEditable(editable)
        self._editable = editable

        self._last_valid_type_index = None

        self._filtered_message_model = FilteredMessageModel(
            extension.known_message_model, self._callbacks
        )

        self._type_list_component = JList(self._filtered_message_model)
        self._type_list_component.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
        self._type_list_component.addListSelectionListener(TypeListListener(self))

        self._new_type_field = JTextField()

        self._component = JSplitPane(JSplitPane.HORIZONTAL_SPLIT)
        self._component.setLeftComponent(self._text_editor.getComponent())
        self._component.setRightComponent(self.createButtonPane())
        self._component.setResizeWeight(0.95)

        self.message_type = None
        self._is_request = None
        self._encoder = None
        self._original_json = None
        self._original_typedef = None
        self._last_set_json = ""
        self._content_info = None
        self._request_content_info = None
        self._request = None
        self._original_content = None

    def getTabCaption(self):
        """Return message tab caption"""
        return "Protobuf"

    def getMessage(self):
        """Transform the JSON format back to the binary protobuf message"""
        try:
            if self.message_type is None or not self.isModified():
                return self._original_content

            json_data = self._text_editor.getText().tostring()

            protobuf_data = blackboxprotobuf.protobuf_from_json(
                json_data, self.message_type
            )

            protobuf_data = self.encodePayload(protobuf_data)
            if "set_protobuf_data" in dir(user_funcs):
                result = user_funcs.set_protobuf_data(
                    protobuf_data,
                    self._original_content,
                    self._is_request,
                    self._content_info,
                    self._helpers,
                    self._request,
                    self._request_content_info,
                )
                if result is not None:
                    return result

            headers = self._content_info.getHeaders()
            return self._helpers.buildHttpMessage(headers, str(protobuf_data))

        except Exception as exc:
            self._callbacks.printError(traceback.format_exc())
            JOptionPane.showMessageDialog(
                self._component, "Error encoding protobuf: " + str(exc)
            )
            # Resets state
            return self._original_content

    def setMessage(self, content, is_request, retry=True):
        """Get the data from the request/response and parse into JSON.
        sets self.message_type
        """
        # Save original content
        self._original_content = content
        if is_request:
            self._content_info = self._helpers.analyzeRequest(
                self._controller.getHttpService(), content
            )
        else:
            self._content_info = self._helpers.analyzeResponse(content)
        self._is_request = is_request
        self._request = None
        self._request_content_info = None

        if not is_request:
            self._request = self._controller.getRequest()
            self._request_content_info = self._helpers.analyzeRequest(
                self._controller.getHttpService(), self._request
            )

        # how we remember which message type correlates to which endpoint
        self._message_hash = self.getMessageHash()

        # Try to find saved messsage type
        self.message_type = None
        self.message_type_name = None
        if self._message_hash in self._extension.saved_types:
            typename = self._extension.saved_types[self._message_hash]
            if typename in default_config.known_types:
                self.message_type_name = typename
                self.message_type = default_config.known_types[typename]
            else:
                del self._extension.saved_types[self._message_hash]

        try:
            protobuf_data = None
            if "get_protobuf_data" in dir(user_funcs):
                protobuf_data = user_funcs.get_protobuf_data(
                    content,
                    is_request,
                    self._content_info,
                    self._helpers,
                    self._request,
                    self._request_content_info,
                )
            if protobuf_data is None:
                protobuf_data = content[self._content_info.getBodyOffset() :].tostring()

            protobuf_data = self.decodePayload(protobuf_data)

            # source_typedef will be the original, updatable version of the dict
            # TODO fix this hack
            self._original_data = protobuf_data
            self._filtered_message_model.set_new_data(protobuf_data)
            self._source_typedef = self.message_type
            json_data, self.message_type = blackboxprotobuf.protobuf_to_json(
                protobuf_data, self.message_type
            )

            self._original_json = json_data
            self._original_typedef = self.message_type
            self._last_set_json = str(json_data)
            self._text_editor.setText(json_data)
            success = True
        except Exception as exc:
            success = False
            self._callbacks.printError(
                "Got error decoding protobuf binary: " + traceback.format_exc()
            )

        # Bring out of exception handler to avoid nexting handlers
        if not success:
            if self._message_hash in self._extension.saved_types:
                del self._extension.saved_types[self._message_hash]
                self.setMessage(content, is_request, False)
                self._text_editor.setText("Error decoding protobuf")

        if self.message_type_name:
            self.forceSelectType(self.message_type_name)

    def decodePayload(self, payload):
        """Add support for decoding a few default methods. Including Base64 and GZIP"""
        if payload.startswith(bytearray([0x1F, 0x8B, 0x08])):
            gzip_decompress = zlib.decompressobj(-zlib.MAX_WBITS)
            self._encoder = "gzip"
            return gzip_decompress.decompress(payload)

        # Try to base64 decode
        try:
            protobuf = base64.b64decode(payload, validate=True)
            self._encoder = "base64"
            return protobuf
        except Exception as exc:
            pass

        # try decoding as a gRPC payload: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
        # we're naiively handling only uncompressed payloads
        if len(payload) > 1 + 4 and payload.startswith(
            bytearray([0x00])
        ):  # gRPC has 1 byte flag + 4 byte length
            (message_length,) = struct.unpack_from(">I", payload[1:])
            if len(payload) == 1 + 4 + message_length:
                self._encoder = "gRPC"
                return payload[1 + 4 :]
        # try:
        #    protobuf = base64.urlsafe_b64decode(payload)
        #    self._encoder = 'base64_url'
        #    return protobuf
        # except Exception as exc:
        #    pass

        self._encoder = None
        return payload

    def encodePayload(self, payload):
        """If we detected an encoding like gzip or base64 when decoding, redo
        that encoding step here
        """
        if self._encoder == "base64":
            return base64.b64encode(payload)
        elif self._encoder == "base64_url":
            return base64.urlsafe_b64encode(payload)
        elif self._encoder == "gzip":
            gzip_compress = zlib.compressobj(-1, zlib.DEFLATED, -zlib.MAX_WBITS)
            self._encoder = "gzip"
            return gzip_compress.compress(payload)
        elif self._encoder == "gRPC":
            message_length = struct.pack(">I", len(payload))
            return bytearray([0x00]) + bytearray(message_length) + payload
        else:
            return payload

    def getSelectedData(self):
        """Get text currently selected in message"""
        return self._text_editor.getSelectedText()

    def getUiComponent(self):
        """Return Java AWT component for this tab"""
        return self._component

    def isEnabled(self, content, is_request):
        """Try to detect a protobuf in the message to enable the tab. Defaults
        to content-type header of 'x-protobuf'. User overridable
        """
        # TODO implement some more default checks
        if is_request:
            info = self._helpers.analyzeRequest(content)
        else:
            info = self._helpers.analyzeResponse(content)

        if "detect_protobuf" in dir(user_funcs):
            result = user_funcs.detect_protobuf(
                content, is_request, info, self._helpers
            )
            if result is not None:
                return result

        # Bail early if there is no body
        if info.getBodyOffset() == len(content):
            return False

        protobuf_content_types = [
            "protobuf",
            "grpc",
        ]
        # Check all headers for x-protobuf
        for header in info.getHeaders():
            if "content-type" in header.lower():
                for protobuf_content_type in protobuf_content_types:
                    if protobuf_content_type in header.lower():
                        return True

        return False

    def isModified(self):
        """Return if the message was modified"""
        return self._text_editor.isTextModified()

    def createButtonPane(self):
        """Create a new button pane for the message editor tab"""
        self._button_listener = EditorButtonListener(self)

        panel = JPanel()
        panel.setLayout(BoxLayout(panel, BoxLayout.Y_AXIS))
        panel.setBorder(EmptyBorder(5, 5, 5, 5))

        panel.add(Box.createRigidArea(Dimension(0, 5)))
        type_scroll_pane = JScrollPane(self._type_list_component)
        type_scroll_pane.setMaximumSize(Dimension(200, 100))
        type_scroll_pane.setMinimumSize(Dimension(150, 100))
        panel.add(type_scroll_pane)
        panel.add(Box.createRigidArea(Dimension(0, 3)))

        new_type_panel = JPanel()
        new_type_panel.setLayout(BoxLayout(new_type_panel, BoxLayout.X_AXIS))
        new_type_panel.add(self._new_type_field)
        new_type_panel.add(Box.createRigidArea(Dimension(3, 0)))
        new_type_panel.add(
            self.createButton(
                "New", "new-type", "Save this message's type under a new name"
            )
        )
        new_type_panel.setMaximumSize(Dimension(200, 20))
        new_type_panel.setMinimumSize(Dimension(150, 20))

        panel.add(new_type_panel)

        button_panel = JPanel()
        button_panel.setLayout(FlowLayout())
        if self._editable:
            button_panel.add(
                self.createButton(
                    "Validate", "validate", "Validate the message can be encoded."
                )
            )
        button_panel.add(
            self.createButton("Edit Type", "edit-type", "Edit the message type")
        )
        button_panel.add(
            self.createButton(
                "Reset Message", "reset", "Reset the message and undo changes"
            )
        )
        button_panel.add(
            self.createButton(
                "Clear Type", "clear-type", "Reparse the message with an empty type"
            )
        )
        button_panel.setMinimumSize(Dimension(100, 200))
        button_panel.setPreferredSize(Dimension(200, 1000))

        panel.add(button_panel)

        return panel

    def createButton(self, text, command, tooltip):
        """Create a new button with the given text and command"""
        button = JButton(text)
        button.setAlignmentX(Component.CENTER_ALIGNMENT)
        button.setActionCommand(command)
        button.addActionListener(self._button_listener)
        button.setToolTipText(tooltip)
        return button

    def validateMessage(self):
        """Callback for validate button. Attempts to encode the message with
        the current type definition
        """
        try:
            json_data = self._text_editor.getText().tostring()
            blackboxprotobuf.protobuf_from_json(json_data, self.message_type)
            # If it works, save the message
            self._original_json = json_data
            self._original_typedef = self.message_type
        except Exception as exc:
            JOptionPane.showMessageDialog(self._component, str(exc))
            self._callbacks.printError(traceback.format_exc())

    def resetMessage(self):
        """Drop any changes and reset the message. Callback for "reset"
        button
        """

        self._last_set_json = str(self._original_json)
        self._text_editor.setText(self._original_json)
        self.message_type = self._original_typedef

    def getMessageHash(self):
        """Compute an "identifier" for the message which is used for sticky
        type definitions. User modifiable
        """
        message_hash = None
        if "hash_message" in dir(user_funcs):
            message_hash = user_funcs.hash_message(
                self._original_content,
                self._is_request,
                self._content_info,
                self._helpers,
                self._request,
                self._request_content_info,
            )
        if message_hash is None:
            # Base it off just the URL and request/response

            content_info = (
                self._content_info if self._is_request else self._request_content_info
            )
            url = content_info.getUrl().getPath()
            message_hash = ":".join([url, str(self._is_request)])
        return message_hash

    def forceSelectType(self, typename):
        index = self._filtered_message_model.get_type_index(typename)
        if index is not None:
            self._last_valid_type_index = index
            self._type_list_component.setSelectedIndex(index)

    def updateTypeSelection(self):
        """Apply a new typedef based on the selected type in the type list"""
        # Check if something is selected
        if self._type_list_component.isSelectionEmpty():
            self._last_valid_type_index = None
            del self._extension.saved_types[self._message_hash]
            return

        # TODO won't actually work right if we delete the type we're using a
        # new type is now in the index
        if self._last_valid_type_index == self._type_list_component.getSelectedIndex():
            # hasn't actually changed since last time we tried
            # otherwise can trigger a second time when we call setSelectedIndex below on failure
            return

        type_name = self._type_list_component.getSelectedValue()
        # try to catch none here...
        if not type_name or type_name not in default_config.known_types:
            return

        try:
            self.applyType(default_config.known_types[type_name])
        except BlackboxProtobufException as exc:
            self._callbacks.printError(traceback.format_exc())

            if isinstance(exc, EncoderException):
                JOptionPane.showMessageDialog(
                    self._component,
                    "Error encoding protobuf with previous type: %s" % (exc),
                )
            elif isinstance(exc, DecoderException):
                JOptionPane.showMessageDialog(
                    self._component,
                    "Error encoding protobuf with type %s: %s" % (type_name, exc),
                )
                # decoder exception means it doesn't match the message that was sucessfully encoded by the prev type
                self._filtered_message_model.remove_type(type_name)

            if self._last_valid_type_index is not None:
                type_name = self._type_list_component.setSelectedIndex(
                    self._last_valid_type_index
                )
            else:
                self._type_list_component.clearSelection()
            return

        self._extension.saved_types[self._message_hash] = type_name
        self._last_valid_type_index = self._type_list_component.getSelectedIndex()

    def editType(self, typedef):
        """Apply the new typedef. Use dict.update to change the original
        dictionary, so we also update the anonymous cached definition and ones
        stored in known_messages"""
        # TODO this is kind of an ugly hack. Should redo how these are referenced
        # probably means rewriting a bunch of the editor
        old_source = self._source_typedef
        old_source.clear()
        old_source.update(typedef)
        self.applyType(old_source)

    def applyType(self, typedef):
        """Apply a new typedef to the message. Throws an exception if type is invalid."""
        # store a reference for later mutation?
        self._source_typedef = typedef
        # Convert to protobuf as old type and re-interpret as new type
        old_message_type = self.message_type
        json_data = self._text_editor.getText().tostring()
        protobuf_data = blackboxprotobuf.protobuf_from_json(json_data, old_message_type)

        new_json, message_type = blackboxprotobuf.protobuf_to_json(
            str(protobuf_data), typedef
        )

        # Should exception out before now if there is an issue
        self.message_type = message_type

        # if the json data was modified, then re-check our types
        if json_data != self._last_set_json:
            self._filtered_message_model.set_new_data(protobuf_data)
        self._last_set_json = str(new_json)
        self._text_editor.setText(str(new_json))

    def saveAsNewType(self):
        """Copy the current type into known_messages"""
        name = self._new_type_field.getText().strip()
        if not NAME_REGEX.match(name):
            JOptionPane.showMessageDialog(
                self._component,
                "%s is not a valid "
                "message name. Message names should be alphanumeric." % name,
            )
            return
        if name in default_config.known_types:
            JOptionPane.showMessageDialog(
                self._component, "Message name %s is " "already taken." % name
            )
            return

        # Do a deep copy on the dictionary so we don't accidentally modify others
        default_config.known_types[name] = copy.deepcopy(self.message_type)
        # update the list of messages. This should trickle down to known message model
        self._extension.known_message_model.addElement(name)
        self._new_type_field.setText("")
        self._extension.saved_types[self._message_hash] = name

        # force select our new type
        self.forceSelectType(name)

    def clearType(self):
        self.applyType({})
        self._type_list_component.clearSelection()
        self._new_type_field.setText("")

    def open_typedef_window(self):
        self._extension.open_typedef_editor(self.message_type, self.editType)