Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jiaxin flask #482

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,11 @@ MigrationBackup/

# Ionide (cross platform F# VS Code tools) working folder
.ionide/

# Restler bin
restler_bin/
Compile/
Fuzz/
Test/
demo_server/coverage_stats.txt
restler
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ See [Testing](./docs/user-guide/Testing.md). To use custom test engine settings,
**Warning:** This type of fuzzing is more aggressive and may create outages in the service under test if the service is poorly implemented (e.g., fuzzing might create resource leaks, perf degradation, backend corruptions, etc.).
See [Fuzzing](./docs/user-guide/Fuzzing.md).

### With warning messages
If you are fuzzing a Flask server and want to have warnings included, use `demo_server/demo_server/wrapper.py` to start the demo server instead of just using `demo_server/demo_server/app.py`.

## Quick Start

For a quick intro with simple examples, see this [Tutorial](./docs/user-guide/TutorialDemoServer.md).
Expand Down
15 changes: 15 additions & 0 deletions commands.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# comments for macos
# compile restler
python3 ./build-restler.py --dest_dir restler_bin/
# compile the engine seperately due to a bug
python3 ./build-restler.py --dest_dir restler_bin/ --compile_type engine --python_path /usr/local/bin/python3.8

# use the engine only
python3 restler.py --restler_grammar /Users/anne/Desktop/restler_project/restler-fuzzer/Compile/grammar.py --no_ssl

# compile grammar
dotnet ./restler_bin/restler/Restler.dll compile --api_spec ./demo_server/swagger.json
# test
dotnet ./restler_bin/restler/Restler.dll test --grammar_file ./Compile/grammar.py --dictionary_file ./Compile/dict.json --settings ./Compile/engine_settings.json --no_ssl
# fuzz
dotnet ./restler_bin/restler/Restler.dll fuzz --grammar_file ./Compile/grammar.py --dictionary_file ./Compile/dict.json --settings ./Compile/engine_settings.json --no_ssl
2 changes: 1 addition & 1 deletion demo_server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ WORKDIR /app

RUN pip install -r requirements.txt

EXPOSE 8888
EXPOSE 8878

ENV FLASK_RUN_HOST=0.0.0.0

Expand Down
6 changes: 5 additions & 1 deletion demo_server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,17 @@ Start demo server at one teminal
--------------------------------
`python demo_server/app.py`

Start demo server which captures warnings at one teminal
--------------------------------
`python demo_server/wrapper.py`

- Make sure you execute all the above from the base directory of demo_server
(the same directory where this README lives in.)

Swagger
==================
- The interactive swagger specification interface will be available at
http://localhost:8888/api/ using your browser.
http://localhost:8878/api/ using your browser.
- The swagger can also be found in the demo_server directory as swagger.json
- The swagger was automatically generated by flask-restplus
- This swagger can be compiled and tested using RESTler.
Expand Down
Empty file added demo_server/db.sqlite
Empty file.
18 changes: 17 additions & 1 deletion demo_server/demo_server/api/blog/business.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

from logging import warning
from demo_server.database.models import Post, Category, db
from flask import abort
from flask import request
import pandas as pd
import json
import warnings

def get_query():
# Gets the query string from the request
Expand Down Expand Up @@ -34,13 +37,26 @@ def get_post(postId):
# PLANTED_BUG -
# Intentionally ignore unexpected query, so the invalid dynamic
# object checker throws a bug due to '200' response.

# PLANTED_BUG for demo purpose
check_use_exist_posts()
post = Post.query.filter(Post.id == postId).one_or_none()
return post or abort(404)

def check_use_exist_posts():
exist_posts = Post.query.limit(5).all()
df = pd.DataFrame()
rows = []
for post in exist_posts:
rows.append([post.id,post.body])
rows.append(['7',"hello"])
df = pd.DataFrame(rows,columns=['id','body'])
df[df['body'] == 'la']['id'] = 10
return

def create_blog_post():
body = request.json.get('body')
post = Post(body)

db.session.add(post)
db.session.commit()
return post
Expand Down
3 changes: 0 additions & 3 deletions demo_server/demo_server/api/blog/endpoints/posts.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from demo_server.api.blog.parsers import pagination_arguments
from demo_server.api.restplus import api
from demo_server.database.models import Post

log = logging.getLogger(__name__)

ns = api.namespace('blog/posts', description='Operations related to blog posts')
Expand All @@ -31,7 +30,6 @@ def get(self):
per_page = args.get('per_page', 1)
posts_query = Post.query
posts_page = posts_query.paginate(page, per_page)

return posts_page

@api.expect(blog_post_public)
Expand Down Expand Up @@ -69,7 +67,6 @@ def delete(self, postId):
"""
Deletes a blog post with matching \"postId\".
"""

delete_post(postId)


Expand Down
8 changes: 3 additions & 5 deletions demo_server/demo_server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import json
from flask import Flask, Blueprint
from demo_server import settings
from settings import SQLALCHEMY_DATABASE_URI
from demo_server.database.models import db
from demo_server.api.blog.endpoints.posts import ns as blog_posts_namespace
# from demo_server.api.blog.endpoints.categories import ns\
Expand All @@ -40,7 +41,6 @@ def configure_app(flask_app):
flask_app.config['RESTPLUS_MASK_SWAGGER'] = settings.RESTPLUS_MASK_SWAGGER
flask_app.config['ERROR_404_HELP'] = settings.RESTPLUS_ERROR_404_HELP


def initialize_app(flask_app):
configure_app(flask_app)

Expand All @@ -49,16 +49,14 @@ def initialize_app(flask_app):
api.add_namespace(blog_posts_namespace)
# api.add_namespace(blog_categories_namespace)
flask_app.register_blueprint(blueprint)

db.init_app(flask_app)


def main():
initialize_app(app)
# with app.app_context():
# db.create_all()
log.info('>>>>> Starting development server at http://{}/api/ <<<<<'.format(app.config['SERVER_NAME']))
app.run(threaded=True, use_reloader=False, debug=settings.FLASK_DEBUG, host=os.getenv("FLASK_RUN_HOST", "localhost"))

if __name__ == "__main__":
main()
# if __name__ == "__main__":
# main()
6 changes: 4 additions & 2 deletions demo_server/demo_server/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@

from datetime import datetime
from flask_sqlalchemy import SQLAlchemy

import pandas as pd
from demo_server import settings
from settings import SQLALCHEMY_DATABASE_URI
db = SQLAlchemy()


class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.Text)
checksum = db.Column(db.Text)

def __init__(self, body):
self.body = body
self.checksum = binascii.b2a_hex(os.urandom(100))[:5]
Expand Down
Binary file modified demo_server/demo_server/db.sqlite
Binary file not shown.
2 changes: 1 addition & 1 deletion demo_server/demo_server/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Licensed under the MIT License.

# Flask settings
FLASK_SERVER_NAME = 'localhost:8888'
FLASK_SERVER_NAME = 'localhost:8878'
FLASK_DEBUG = True # Do not use debug mode in production

# Flask-Restplus settings
Expand Down
61 changes: 61 additions & 0 deletions demo_server/demo_server/wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from distutils.log import warn
from logging import warning
from pydoc import resolve
from flask import Flask, request, g
from app import app, main
import warnings
from pandas.core.common import SettingWithCopyWarning

warning_map = {
"Warning": 289,
"UserWarning": 290,
"DeprecationWarning": 291,
"SyntaxWarning": 292,
"RuntimeWarning": 293,
"FutureWarning": 294,
"PendingDeprecationWarning": 295,
"ImportWarning": 296,
"UnicodeWarning": 297,
"BytesWarning": 298,
"ResourceWarning": 299
}

with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")

@app.before_request
def before_req():
# print('request intercepted before processing')
# print(request)

# clear warnings before processing requests
w.clear()

@app.after_request
def after_req(response):
print('response intercepted after processing')
print(response)

# We can access captured warnings in w
print('When processing the reqeust, the following warnings were observed')
print(w)

if len(w) > 0:
# Only return the first warning code...
warning_category = w[0].category.__name__
status_code = warning_map.get(warning_category, warning_map["Warning"])
response.status_code = status_code

# Concat message
warning_msg = ''
for warning in w:
warning_msg += str(warning.message) + '\n'
print(warning_msg)

print('response after warning check')
print(response)
# clear warnings after response
w.clear()
return response

main()
4 changes: 2 additions & 2 deletions demo_server/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
flask==1.1.2
flask==1.1.02
flask-restplus==0.13.0
Flask-SQLAlchemy==2.5.1
itsdangerous==2.0.1
Werkzeug==0.16.0

itsdangerous==2.0.1
2 changes: 1 addition & 1 deletion demo_server/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -285,5 +285,5 @@
},
"NoResultFound": {}
},
"host": "localhost:8888"
"host": "localhost:8878"
}
4 changes: 2 additions & 2 deletions docs/user-guide/Replay.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ so that the sequence can be replayed exactly as it was executed before.
Below is an example of the requests and responses from a replay log sequence.

```
-> POST /api/blog/posts HTTP/1.1\r\nAccept: application/json\r\nHost: localhost:8888\r\nContent-Type: application/json\r\n\r\n{\n "id":0,\n "body":"fuzzstring"}\r\n
-> POST /api/blog/posts HTTP/1.1\r\nAccept: application/json\r\nHost: localhost:8878\r\nContent-Type: application/json\r\n\r\n{\n "id":0,\n "body":"fuzzstring"}\r\n
! producer_timing_delay 0
! max_async_wait_time 0
PREVIOUS RESPONSE: 'HTTP/1.1 201 CREATED\r\nContent-Type: application/json\r\nContent-Length: 45\r\nServer: Werkzeug/0.16.0 Python/3.8.2\r\nDate: Thu, 01 Oct 2020 22:00:27 GMT\r\n\r\n{\n "id": 5875,\n "body": "fuzzstring"\n}\n'

-> PUT /api/blog/posts/5875 HTTP/1.1\r\nAccept: application/json\r\nHost: localhost:8888\r\nContent-Type: application/json\r\n\r\n{"body":"fuzzstring"}
-> PUT /api/blog/posts/5875 HTTP/1.1\r\nAccept: application/json\r\nHost: localhost:8878\r\nContent-Type: application/json\r\n\r\n{"body":"fuzzstring"}
! producer_timing_delay 0
! max_async_wait_time 0
PREVIOUS RESPONSE: 'HTTP/1.1 500 INTERNAL SERVER ERROR\r\nContent-Type: application/json\r\nContent-Length: 176\r\nServer: Werkzeug/0.16.0 Python/3.8.2\r\nDate: Thu, 01 Oct 2020 22:00:28 GMT\r\n\r\n{\n "message": "The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application."\n}\n'
Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/Testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ During each Test run a `speccov.json` file will be created in the logs directory
"request_uri": "/api/blog/posts/5872",
"request_headers": [
"Accept: application/json",
"Host: localhost:8888",
"Host: localhost:8878",
"Content-Type: application/json"
],
"request_body": "{\n \"id\":\"5872\",\n \"checksum\":\"fuzzstring\",\n \"body\":\"first blog\"}\r\n",
Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/TutorialDemoServer.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ In this example, coverage is 5 / 6 and the only INVALID request is
+ restler_fuzzable_int: ['0', '1']
- restler_static_string: ' HTTP/1.1\r\n'
- restler_static_string: 'Accept: application/json\r\n'
- restler_static_string: 'Host: localhost:8888\r\n'
- restler_static_string: 'Host: localhost:8878\r\n'
- restler_static_string: '\r\n'

By looking at `network.testing.<...>.txt`, we can see that RESTler attempts to execute this request 4 times, each time with a value either 0 or 1 for the `per_page=` and `page=`. It turns out none of these 4 combinations are valid: the `per_page=` must be 2 minimally, but RESTler was not able to infer this automatically. (One way to fix this is to edit `dict.json` and add the value `2` in the list for `restler_fuzzable_int`.)
Expand Down
29 changes: 29 additions & 0 deletions motivating_server/CoursesStudentNamesID/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from flask import Flask, jsonify, request, abort

app = Flask(__name__)

@app.route("/get_class_names")
def get_class_names():
classes = ['compiler', 'physics', 'automated_testing']
return jsonify(classes)

# /get_students_and_ids?class_name=compiler
@app.route("/get_students_and_ids")
def get_students_and_ids():
class_name = request.args.get('class_name', '')
if class_name == 'compiler':
students_and_ids = [('Aatrox', '1111111111'), ('Ahri', '22222222'), ('Akali', '333333333')]
return jsonify(students_and_ids)
elif class_name == 'physics':
students_and_ids = [('Elise', '24253245'), ('Ekko', '23645126'), ('Jax', '7643325345')]
return jsonify(students_and_ids)
elif class_name == 'automated_testing':
# Notice there's a missing comma in the list below
students_and_ids = [('Sett', '425145'), ('Urgot', '52342543')('Ziggs', '5722145')]
return jsonify(students_and_ids)
else:
# Not found
abort(404)

if __name__ == '__main__':
app.run(host='0.0.0.0', port=11111)
46 changes: 46 additions & 0 deletions motivating_server/LRUCache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""
Modified from
https://www.geeksforgeeks.org/lru-cache-in-python-using-ordereddict/
"""

from collections import OrderedDict
from random import randrange


class LRUCache:
def __init__(self):
self.cache = OrderedDict()

def get(self, key):
if key not in self.cache:
return -1
else:
self.cache.move_to_end(key)
return self.cache[key]

def put(self, key, value):
self.cache[key] = value
self.cache.move_to_end(key)

# 1000 is the hard-coded LRU cache capacity
# The line below will raise a 'warning'
if 1000 is (len(self.cache) - 1):
self.cache.popitem(last=False)


if __name__ == '__main__':
# RUNNER
# initializing our cache with the capacity of 1000
cache = LRUCache()

num_test_records = 10000

for idx in range(num_test_records):
key, value = idx, randrange(10000)
cache.put(key, value)
cache_len = len(cache.cache)

print(f'Number of entries in LRU cache is {cache_len}')



Loading