├── .gitignore
├── .gitmodules
├── README.md
├── __init__.py
├── extender.py
├── images
├── burp-installation-final.png
├── burp-installation.png
├── protoburp-extension-tab.png
└── protoburp-transformation.png
├── json-generator.py
├── log.txt
├── protobuf-encoder.py
├── setup.sh
├── tab.py
└── test_app
├── __init__.py
├── addressbook.proto
├── app.py
├── complexmessage.proto
├── employee.proto
└── userprofile.proto
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "deps/protobuf"]
2 | path = deps/protobuf
3 | url = https://github.com/protocolbuffers/protobuf.git
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ProtoBurp
2 | Check out my blog post detailing the wonderful things you can do with `ProtoBurp`! [https://dillonfrankesecurity.com/posts/protoburp-encode-custom-protobuf-messages-in-burp/](https://dillonfrankesecurity.com/posts/protoburp-encode-custom-protobuf-messages-in-burp/)
3 |
4 | ## Description
5 | `ProtoBurp` is a Burp Suite extension that enables security researchers to encode and fuzz custom Protobuf messages. It allows users to automatically convert JSON data into a Protobuf message based on a provided protobuf definition file. This opens up opportunities for fuzzing inputs using Burp's Repeater and Intruder tools, as well as proxy traffic from other tools (e.g. `sqlmap`).
6 |
7 | ## Installation
8 |
9 | ### 1. Clone the `ProtoBurp` repository and its submodules
10 | ```bash
11 | git clone --recursive https://github.com/dillonfranke/protoburp.git
12 | ```
13 | > Make sure to add the `--recursive` option
14 | ### 2. Install the `protoc` utility, which you'll need to compile Protobuf defintion (`.proto`) files
15 | Mac:
16 | ```bash
17 | brew install protobuf
18 | ```
19 | Debian Linux:
20 | ```bash
21 | sudo apt-get update
22 | sudo apt-get install protobuf-compiler
23 | ```
24 | Windows:
25 | https://github.com/protocolbuffers/protobuf/releases
26 |
27 | ### 3. Run the `setup.sh` script
28 | This will install Google's `protobuf` module so it can be used by the extension
29 | ```bash
30 | sudo ./setup.sh
31 | ```
32 |
33 | ## Usage
34 |
35 | ### 1. Compile the `.proto` file you want to convert into Python format
36 | > Several example `.proto` files are contained in the `test_app` folder
37 | ```bash
38 | protoc --python_out=. addressbook.proto
39 | ```
40 |
41 | ### 2. Load the `ProtoBurp` extension and select your compiled `.proto` file
42 |
43 |
44 |
45 |
46 |
47 | - Click 'Enable ProtoBurp'
48 | - Select the Python Protobuf definition file you just compiled
49 |
50 |
51 |
52 | ### 3. Set the `ProtoBurp` header on your requests, and your requests will be transformed from JSON to Protobuf!
53 |
54 |
55 | ### Generating a JSON payload
56 | You might be wondering: "How can I generate a JSON object from a `.proto` file to use with `ProtoBurp`?"
57 |
58 | Easy, I wrote a script that, given a `.proto` file, will fill in placeholder values to generate a JSON payload. You can then use the JSON payload with `ProtoBurp`. Here's how you use the script:
59 |
60 | ```bash
61 | ❯ python3 json-generator.py
62 | Usage: python3 json-generator.py
63 | ```
64 | ```bash
65 | ❯ python3 json-generator.py test_app/addressbook_pb2.py AddressBook
66 | {
67 | "people": [
68 | {
69 | "name": "example",
70 | "id": 1,
71 | "email": "example",
72 | "phones": [
73 | {
74 | "number": "example",
75 | "type": "PHONE_TYPE_UNSPECIFIED"
76 | },
77 | {
78 | "number": "example",
79 | "type": "PHONE_TYPE_UNSPECIFIED"
80 | }
81 | ]
82 | },
83 | {
84 | "name": "example",
85 | "id": 1,
86 | "email": "example",
87 | "phones": [
88 | {
89 | "number": "example",
90 | "type": "PHONE_TYPE_UNSPECIFIED"
91 | },
92 | {
93 | "number": "example",
94 | "type": "PHONE_TYPE_UNSPECIFIED"
95 | }
96 | ]
97 | }
98 | ]
99 | }
100 | ```
101 |
102 | ## Use Cases
103 | Please see my [blog post](https://dillonfrankesecurity.com/posts/protoburp-encode-custom-protobuf-messages-in-burp/), where I talk about how you can use `ProtoBurp` with Repeater, Intruder, and external security utilities like `sqlmap`!
104 |
105 | ## Bugs and Feature Requests
106 | Please use the [issues tab](https://github.com/dillonfranke/protoburp/issues) for any bugs or feature requests.
107 |
108 | Happy Hunting!
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import inspect
4 |
5 | _BASE_DIR = os.path.abspath(
6 | os.path.dirname(inspect.getfile(inspect.currentframe()))
7 | )
8 |
9 | sys.path.insert(0, _BASE_DIR + "/deps/protobuf/python/")
10 | sys.path.insert(0, _BASE_DIR + "/gen")
11 |
12 |
--------------------------------------------------------------------------------
/extender.py:
--------------------------------------------------------------------------------
1 | import json
2 | import subprocess
3 | import inspect
4 | import base64
5 | from struct import pack
6 |
7 | from burp import IBurpExtender
8 | from burp import IHttpListener
9 | from burp import ITab
10 | from javax.swing import JPanel
11 | from javax.swing import JButton
12 | from javax.swing import JFileChooser
13 | from java.awt import FlowLayout
14 | from tab import Tab
15 |
16 | # Add correct directory to sys.path
17 | _BASE_DIR = os.path.abspath(
18 | os.path.dirname(inspect.getfile(inspect.currentframe()))
19 | )
20 |
21 | sys.path.insert(0, _BASE_DIR + "/deps/protobuf/python/")
22 |
23 | EXTENSION_NAME = "ProtoBurp"
24 |
25 | class BurpExtender(IBurpExtender, IHttpListener):
26 |
27 | # Implement IBurpExtender methods
28 | def registerExtenderCallbacks(self, callbacks):
29 | # keep a reference to our callbacks object (Burp Extensibility Feature)
30 | self._callbacks = callbacks
31 |
32 | # set our extension name that will display in the Extender tool when loaded
33 | self._callbacks.setExtensionName(EXTENSION_NAME)
34 |
35 | # register ourselves as an HTTP listener
36 | self._callbacks.registerHttpListener(self)
37 |
38 | # get burp helper functions
39 | self._helpers = self._callbacks.getHelpers()
40 |
41 | self.suite_tab = Tab(self, callbacks)
42 |
43 | # Add the custom tab
44 | callbacks.addSuiteTab(self.suite_tab)
45 |
46 | def getTabCaption(self):
47 | return "ProtoBurp"
48 |
49 | def getUiComponent(self):
50 | return self._jPanel
51 |
52 | def file_chooser(self, event):
53 | chooser = JFileChooser()
54 | action = chooser.showOpenDialog(self._jPanel)
55 |
56 | if action == JFileChooser.APPROVE_OPTION:
57 | file = chooser.getSelectedFile()
58 |
59 | def json_to_protobuf_in_python3(self, json_body):
60 | # Prepare the command to run in Python 3
61 | cmd = ["python3", os.path.join(_BASE_DIR, "protobuf-encoder.py"), "--json", json.dumps(json_body), "--protobuf_definition", str(self.suite_tab.selectedFilePath)]
62 |
63 | output = ""
64 | # Run the command
65 | try:
66 | output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
67 | except subprocess.CalledProcessError as e:
68 | print("Subprocess exited with error (status code {}):".format(e.returncode))
69 | print(e.output.decode())
70 | output = output.decode("utf-8").strip()
71 | protobuf = base64.b64decode(output)
72 |
73 | return protobuf
74 |
75 | # Implement IHttpListener methods
76 | def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
77 | # Only continue if the extension is enabled
78 | if not self.suite_tab.protoburp_enabled:
79 | return
80 | # Only process requests
81 | if not messageIsRequest:
82 | return
83 |
84 | # Get the HTTP service for the request
85 | httpService = messageInfo.getHttpService()
86 | # Convert the request to a IRequestInfo object
87 | requestInfo = self._helpers.analyzeRequest(httpService, messageInfo.getRequest())
88 | # requestInfo is an IRequestInfo object
89 | headers = requestInfo.getHeaders()
90 |
91 | # Convert header names to lower case for case-insensitive comparison
92 | header_names = [header.split(":")[0].lower() for header in headers]
93 |
94 | # Only process if the ProtoBurp header exists
95 | if not "protoburp" in header_names:
96 | return
97 |
98 | # Get the body of the request
99 | body = messageInfo.getRequest()[requestInfo.getBodyOffset():]
100 | # Convert the body from bytes to string
101 | body_string = body.tostring().decode()
102 | # Convert the string to a JSON object
103 | json_body = json.loads(body_string)
104 | # Convert the JSON to Protobuf
105 | protobuf = self.json_to_protobuf_in_python3(json_body)
106 |
107 | #For header value that contains application/grpc content type
108 | if any("application/grpc" in header.lower() for header in headers):
109 | # Calculate the length of the serialized message.
110 | message_length = len(protobuf)
111 |
112 | # Create the gRPC message header.
113 | # It consists of a compressed flag (0 for uncompressed) and the message length.
114 | grpc_header = pack('>B', 0) + pack('>I', message_length)
115 |
116 | # Concatenate the header and the serialized message.
117 | protobuf = grpc_header + protobuf
118 |
119 | # Create a new HTTP message with the Protobuf body
120 | new_message = self._helpers.buildHttpMessage(headers, protobuf)
121 | # Update the request in the messageInfo object
122 | messageInfo.setRequest(new_message)
123 |
--------------------------------------------------------------------------------
/images/burp-installation-final.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dillonfranke/protoburp/249b4eaea52ea99b53003b04cd911a6d0125ced5/images/burp-installation-final.png
--------------------------------------------------------------------------------
/images/burp-installation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dillonfranke/protoburp/249b4eaea52ea99b53003b04cd911a6d0125ced5/images/burp-installation.png
--------------------------------------------------------------------------------
/images/protoburp-extension-tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dillonfranke/protoburp/249b4eaea52ea99b53003b04cd911a6d0125ced5/images/protoburp-extension-tab.png
--------------------------------------------------------------------------------
/images/protoburp-transformation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dillonfranke/protoburp/249b4eaea52ea99b53003b04cd911a6d0125ced5/images/protoburp-transformation.png
--------------------------------------------------------------------------------
/json-generator.py:
--------------------------------------------------------------------------------
1 | import os
2 | import inspect
3 | import sys
4 | # Add correct directory to sys.path
5 | _BASE_DIR = os.path.abspath(
6 | os.path.dirname(inspect.getfile(inspect.currentframe()))
7 | )
8 |
9 | sys.path.insert(0, _BASE_DIR + "/deps/protobuf/python/")
10 |
11 | from google.protobuf.json_format import MessageToJson
12 | import importlib
13 | import importlib.util
14 |
15 | def set_placeholder_values(message):
16 | for field in message.DESCRIPTOR.fields:
17 | try:
18 | if field.type == field.TYPE_DOUBLE or field.type == field.TYPE_FLOAT:
19 | if field.label == field.LABEL_REPEATED:
20 | getattr(message, field.name).extend([1.1, 2.2])
21 | else:
22 | setattr(message, field.name, 1.1)
23 | elif field.type == field.TYPE_INT64 or field.type == field.TYPE_UINT64 or \
24 | field.type == field.TYPE_INT32 or field.type == field.TYPE_FIXED64 or \
25 | field.type == field.TYPE_FIXED32 or field.type == field.TYPE_UINT32 or \
26 | field.type == field.TYPE_SFIXED32 or field.type == field.TYPE_SFIXED64 or \
27 | field.type == field.TYPE_SINT32 or field.type == field.TYPE_SINT64:
28 | if field.label == field.LABEL_REPEATED:
29 | getattr(message, field.name).extend([1, 2])
30 | else:
31 | setattr(message, field.name, 1)
32 | elif field.type == field.TYPE_BOOL:
33 | if field.label == field.LABEL_REPEATED:
34 | getattr(message, field.name).extend([True, False])
35 | else:
36 | setattr(message, field.name, True)
37 | elif field.type == field.TYPE_STRING:
38 | if field.label == field.LABEL_REPEATED:
39 | getattr(message, field.name).extend(['example1', 'example2'])
40 | else:
41 | setattr(message, field.name, 'example')
42 | elif field.type == field.TYPE_BYTES:
43 | if field.label == field.LABEL_REPEATED:
44 | getattr(message, field.name).extend([b'example1', b'example2'])
45 | else:
46 | setattr(message, field.name, b'example')
47 | elif field.type == field.TYPE_ENUM:
48 | if field.label == field.LABEL_REPEATED:
49 | getattr(message, field.name).extend([list(field.enum_type.values_by_name.values())[i].number for i in range(2)])
50 | else:
51 | setattr(message, field.name, list(field.enum_type.values_by_name.values())[0].number)
52 |
53 | elif field.type == field.TYPE_MESSAGE:
54 | if field.label == field.LABEL_REPEATED:
55 | nested_message = getattr(message, field.name).add()
56 | set_placeholder_values(nested_message)
57 | nested_message = getattr(message, field.name).add()
58 | set_placeholder_values(nested_message)
59 | else:
60 | nested_message = getattr(message, field.name)
61 | set_placeholder_values(nested_message)
62 | except Exception as e:
63 | sys.stderr.write(f"Had an issue with the {field.name} field, so keeping it uninitialized...")
64 |
65 | def generate_example_json(protobuf_module, message_name):
66 | # Get the protobuf message class
67 | message_class = getattr(protobuf_module, message_name)
68 | # Instantiate the message class
69 | message = message_class()
70 |
71 | set_placeholder_values(message)
72 |
73 | # Convert the protobuf message to a JSON string and return it
74 | return MessageToJson(message, preserving_proto_field_name=True)
75 |
76 | def main():
77 | try:
78 | protobuf_definition_path = sys.argv[1]
79 | message_name = sys.argv[2]
80 | except:
81 | print("Usage: python3 json-generator.py ")
82 | exit(1)
83 | # Add the directory of protobuf_definition_path to sys.path
84 | sys.path.insert(0, os.path.dirname(os.path.abspath(protobuf_definition_path)))
85 | # Dynamically import the module
86 | protobuf_module = importlib.import_module(os.path.splitext(os.path.basename(protobuf_definition_path))[0])
87 |
88 | # Generate and print an example JSON object
89 | json_obj = generate_example_json(protobuf_module, message_name)
90 | print(json_obj)
91 |
92 | if __name__ == '__main__':
93 | main()
94 |
95 |
--------------------------------------------------------------------------------
/log.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/protobuf-encoder.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | import argparse
4 | import json
5 | import base64
6 | import importlib.util
7 | import inspect
8 |
9 | _BASE_DIR = os.path.abspath(
10 | os.path.dirname(inspect.getfile(inspect.currentframe()))
11 | )
12 | sys.path.insert(0, _BASE_DIR + "/deps/protobuf/python/")
13 | from google.protobuf.json_format import Parse
14 | from google.protobuf.message import Message
15 |
16 | def main():
17 | with open('log.txt', 'w') as logfile:
18 |
19 | # Parse arguments
20 | logfile.write("Parsing Args...\n")
21 | parser = argparse.ArgumentParser()
22 | parser.add_argument("--json")
23 | parser.add_argument("--protobuf_definition")
24 | args = parser.parse_args()
25 |
26 | # Load protobuf module from specified file
27 | logfile.write("Loading protobuf module...\n")
28 | sys.path.insert(0, os.path.dirname(args.protobuf_definition))
29 |
30 | # Get the filename with the extension
31 | base_name = os.path.basename(args.protobuf_definition)
32 |
33 | # Remove the extension
34 | class_name = os.path.splitext(base_name)[0]
35 | proto_module = __import__(class_name)
36 |
37 | proto_class = None
38 |
39 | for name, obj in inspect.getmembers(proto_module):
40 | if inspect.isclass(obj) and issubclass(obj, Message):
41 | proto_class = getattr(proto_module, name)
42 | break
43 |
44 | # Convert JSON to protobuf
45 | proto_msg = proto_class()
46 | logfile.write("Parsing JSON string into protobuf\n")
47 | Parse(args.json, proto_msg)
48 |
49 | logfile.write("Serializing protobuf structure to string\n")
50 | serialized_protobuf = proto_msg.SerializeToString()
51 |
52 | # Print the resulting protobuf
53 | logfile.write("Done, returning base64 encoded string\n")
54 | logfile.write(base64.b64encode(serialized_protobuf).decode())
55 | print(base64.b64encode(serialized_protobuf).decode())
56 |
57 |
58 | main()
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/setup.sh:
--------------------------------------------------------------------------------
1 | cd deps/protobuf/python
2 | python3 setup.py install
3 |
--------------------------------------------------------------------------------
/tab.py:
--------------------------------------------------------------------------------
1 | from burp import ITab
2 | from javax.swing import JSplitPane, JScrollPane, JPanel, JButton, BoxLayout, Box
3 | from javax.swing import JLabel, JCheckBox, JTextArea, JList, ListSelectionModel, JFileChooser
4 | from javax.swing import DefaultListModel
5 | from javax.swing.border import EmptyBorder
6 | from java.awt import Component, Dimension, BorderLayout
7 |
8 | class Tab(ITab):
9 | """Burp tab for selecting a compile Protobuf file and enabling/disabling the extension"""
10 |
11 | def __init__(self, extension, burp_callbacks):
12 | self.protoburp_enabled = False
13 | self._burp_callbacks = burp_callbacks
14 | self._extension = extension
15 | self.selectedFilePath = None
16 |
17 | self._type_list_component = JList(DefaultListModel())
18 | self._type_list_component.setSelectionMode(
19 | ListSelectionModel.MULTIPLE_INTERVAL_SELECTION
20 | )
21 |
22 | self._component = JPanel()
23 | self._component.setLayout(BorderLayout())
24 | self._component.setBorder(EmptyBorder(10, 10, 10, 10))
25 |
26 | # Add instruction label
27 | instructionText = """
28 | Welcome to ProtoBurp! ProtoBurp converts JSON data into Protobuf messages based on a provided `.proto` file. This allows you to use Repeater or Intruder to quickly fuzz endpoints accepting Protobufs.
29 |
30 | To use this extension, please follow the steps below:
31 | 1. Create or obtain a `.proto` file you'd like to create protobuf messages
32 | 2. Use the `protoc` utility to compile your `.proto` file into Python format. (e.g. `protoc --python_out=./ MyMessage.proto`)
33 | 3. Click the 'Choose File' button to select your compiled protobuf file.
34 | 4. Check the 'Enable ProtoBurp' checkbox.
35 | 5. All requests sent with the header `ProtoBurp: True` will then be converted from JSON to a Protobuf!
36 | """
37 | instructions = JTextArea(instructionText)
38 | instructions.setEditable(False) # Make the text area non-editable
39 | self._component.add(instructions, BorderLayout.PAGE_START)
40 |
41 | # Add file chooser button and checkbox
42 | topPanel = JTextArea()
43 | topPanel.setLayout(BoxLayout(topPanel, BoxLayout.Y_AXIS)) # Arrange components vertically
44 |
45 | fileLabel = JLabel("Compiled Protobuf File (Python Format): ")
46 | button = JButton("Choose File", actionPerformed=self.chooseFile)
47 | fileChooserPanel = JPanel() # A new panel to hold the file chooser components
48 | fileChooserPanel.add(fileLabel)
49 | fileChooserPanel.add(button)
50 | self._label = JLabel("No file chosen")
51 | fileChooserPanel.add(self._label)
52 |
53 | # Add option to enable/disable ProtoBurp
54 | enableProtoBurp = JCheckBox("Enable ProtoBurp", actionPerformed=self.toggleEnabled)
55 |
56 | topPanel.add(enableProtoBurp)
57 | topPanel.add(fileChooserPanel)
58 |
59 |
60 | self._component.add(topPanel, BorderLayout.CENTER)
61 |
62 | def toggleEnabled(self, event):
63 | self.protoburp_enabled = not self.protoburp_enabled
64 | print(self.protoburp_enabled)
65 |
66 | def chooseFile(self, event):
67 | chooser = JFileChooser()
68 | action = chooser.showOpenDialog(None)
69 |
70 | if action == JFileChooser.APPROVE_OPTION:
71 | file = chooser.getSelectedFile()
72 | self.selectedFilePath = file.getAbsolutePath()
73 | self._label.text = "Selected file: " + self.selectedFilePath
74 | print("Selected file: " + self.selectedFilePath)
75 |
76 | def getTabCaption(self):
77 | """Returns name on tab"""
78 | return "ProtoBurp"
79 |
80 | def getUiComponent(self):
81 | """Returns Java AWT component for tab"""
82 | return self._component
83 |
--------------------------------------------------------------------------------
/test_app/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dillonfranke/protoburp/249b4eaea52ea99b53003b04cd911a6d0125ced5/test_app/__init__.py
--------------------------------------------------------------------------------
/test_app/addressbook.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package tutorial;
4 |
5 | message Person {
6 | string name = 1;
7 | int32 id = 2;
8 | string email = 3;
9 |
10 | enum PhoneType {
11 | PHONE_TYPE_UNSPECIFIED = 0;
12 | PHONE_TYPE_MOBILE = 1;
13 | PHONE_TYPE_HOME = 2;
14 | PHONE_TYPE_WORK = 3;
15 | }
16 |
17 | message PhoneNumber {
18 | string number = 1;
19 | PhoneType type = 2;
20 | }
21 |
22 | repeated PhoneNumber phones = 4;
23 | }
24 |
25 | message AddressBook {
26 | repeated Person people = 1;
27 | }
28 |
--------------------------------------------------------------------------------
/test_app/app.py:
--------------------------------------------------------------------------------
1 | import os
2 | import inspect
3 | import sys
4 | # Add correct directory to sys.path
5 | _BASE_DIR = os.path.abspath(
6 | os.path.dirname(inspect.getfile(inspect.currentframe()))
7 | )
8 |
9 | sys.path.insert(0, _BASE_DIR + "/../deps/protobuf/python/")
10 |
11 | import json
12 | from flask import Flask, request
13 | from flask_restful import Resource, Api
14 | from flask_sqlalchemy import SQLAlchemy
15 | from sqlalchemy import UniqueConstraint
16 | from sqlalchemy.engine import Engine
17 | from google.protobuf.json_format import MessageToJson, Parse
18 | import addressbook_pb2
19 |
20 | app = Flask(__name__)
21 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
22 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
23 | api = Api(app)
24 | db = SQLAlchemy(app)
25 |
26 | @db.event.listens_for(Engine, "connect")
27 | def set_sqlite_pragma(dbapi_connection, connection_record):
28 | cursor = dbapi_connection.cursor()
29 | cursor.execute("PRAGMA foreign_keys=ON;")
30 | cursor.close()
31 |
32 | class PersonModel(db.Model):
33 | __tablename__ = 'person'
34 | id = db.Column(db.Integer, primary_key=True)
35 | name = db.Column(db.String(50), nullable=False)
36 | email = db.Column(db.String(50), unique=True)
37 | phones = db.relationship('PhoneModel', backref='person', lazy=True)
38 |
39 | class PhoneModel(db.Model):
40 | __tablename__ = 'phone'
41 | id = db.Column(db.Integer, primary_key=True)
42 | number = db.Column(db.String(50), nullable=False)
43 | type = db.Column(db.String(50))
44 | person_id = db.Column(db.Integer, db.ForeignKey('person.id'), nullable=False)
45 |
46 |
47 | with app.app_context():
48 | db.create_all()
49 |
50 |
51 | PHONE_TYPE_MAP = {
52 | "0": "PHONE_TYPE_UNSPECIFIED",
53 | "1": "PHONE_TYPE_MOBILE",
54 | "2": "PHONE_TYPE_HOME",
55 | "3": "PHONE_TYPE_WORK"
56 | }
57 |
58 | class AddressBook(Resource):
59 | def get(self):
60 | people = PersonModel.query.all()
61 | address_book = addressbook_pb2.AddressBook()
62 | for person in people:
63 | p = addressbook_pb2.Person(name=person.name, id=person.id, email=person.email)
64 | for phone in person.phones:
65 | phone_type_label = PHONE_TYPE_MAP[phone.type]
66 | p.phones.add(number=phone.number, type=phone_type_label)
67 | address_book.people.append(p)
68 | json_dict = json.loads(MessageToJson(address_book))
69 | return json_dict, 200
70 |
71 | def post(self):
72 | address_book = addressbook_pb2.AddressBook()
73 | address_book.ParseFromString(request.data)
74 | for person in address_book.people:
75 | p = PersonModel(name=person.name, id=person.id, email=person.email)
76 | for phone in person.phones:
77 | p.phones.append(PhoneModel(number=phone.number, type=phone.type))
78 | db.session.add(p)
79 | db.session.commit()
80 | return 'Added', 201
81 |
82 |
83 | api.add_resource(AddressBook, '/addressbook')
84 |
85 |
86 | if __name__ == '__main__':
87 | app.run(debug=True)
88 |
89 |
--------------------------------------------------------------------------------
/test_app/complexmessage.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package complex;
4 |
5 | message ComplexMessage {
6 | int32 id = 1;
7 |
8 | message SubMessage {
9 | string name = 1;
10 | int64 timestamp = 2;
11 | bytes data = 3;
12 |
13 | enum SubEnum {
14 | UNKNOWN = 0;
15 | FIRST = 1;
16 | SECOND = 2;
17 | }
18 | SubEnum state = 4;
19 |
20 | message NestedMessage {
21 | float percentage = 1;
22 | string status = 2;
23 | }
24 | NestedMessage details = 5;
25 | }
26 | SubMessage sub = 2;
27 |
28 | enum EnumType {
29 | UNDEFINED = 0;
30 | TYPE_A = 1;
31 | TYPE_B = 2;
32 | }
33 | EnumType type = 3;
34 |
35 | repeated int32 numbers = 4;
36 | map mappings = 5;
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/test_app/employee.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package example;
3 |
4 | message Employee {
5 | string name = 1;
6 | int32 id = 2;
7 |
8 | enum JobTitle {
9 | UNDEFINED = 0;
10 | ENGINEER = 1;
11 | MANAGER = 2;
12 | DIRECTOR = 3;
13 | }
14 |
15 | JobTitle title = 3;
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/test_app/userprofile.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package example;
3 |
4 | message UserProfile {
5 | string username = 1;
6 | int32 id = 2;
7 |
8 | message Address {
9 | string street = 1;
10 | string city = 2;
11 | string state = 3;
12 | string zip = 4;
13 | }
14 |
15 | Address address = 3;
16 |
17 | message Contact {
18 | string phone = 1;
19 | string email = 2;
20 | }
21 |
22 | repeated Contact contacts = 4;
23 | }
24 |
25 |
--------------------------------------------------------------------------------