This repository has been archived by the owner on Nov 4, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 169
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
184 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import sys | ||
from datetime import datetime, timedelta | ||
|
||
from flask import Blueprint, jsonify, request | ||
|
||
import httpobs.database as database | ||
import httpobs.scanner as scanner | ||
from httpobs import STATE_FAILED | ||
from httpobs.conf import API_COOLDOWN, DEVELOPMENT_MODE | ||
from httpobs.scanner.grader import get_score_description | ||
from httpobs.website import add_response_headers | ||
from httpobs.website.utils import valid_hostname | ||
|
||
api_v2 = Blueprint("api_v2", __name__) | ||
|
||
|
||
@api_v2.route("/analyze", methods=["GET", "OPTIONS", "POST"]) | ||
@add_response_headers(cors=True) | ||
def api_post_scan_hostname(): | ||
status_code = 200 | ||
scan = {} | ||
tests = {} | ||
|
||
host = request.args.get("host", "").lower().strip() | ||
try: | ||
site_id = database.select_site_id(host, create=False) | ||
except IOError: | ||
return { | ||
"error": "database-down", | ||
"text": "Unable to connect to database", | ||
}, 500 | ||
|
||
if site_id is not None: | ||
hostname = host | ||
else: | ||
ip = True if valid_hostname(host) is None else False | ||
if ip: | ||
return { | ||
"error": "invalid-hostname-ip", | ||
"text": "Cannot scan IP addresses", | ||
}, 400 | ||
|
||
hostname = valid_hostname(host) or ( | ||
valid_hostname("www." + host) if host else False | ||
) # prepend www. if necessary | ||
if not hostname: | ||
return { | ||
"error": "invalid-hostname", | ||
"text": f"{host} is an invalid hostname", | ||
}, 400 | ||
|
||
site_id: int = database.select_site_id(host, create=True) | ||
scan = database.select_scan_most_recent_scan(site_id) | ||
|
||
if scan and request.method == "POST": | ||
time_since_scan = datetime.now() - scan["end_time"] | ||
if time_since_scan < timedelta(seconds=API_COOLDOWN): | ||
status_code = 429 | ||
else: | ||
scan = None | ||
|
||
if scan: | ||
scan_id = scan["id"] | ||
|
||
tests = database.select_test_results(scan_id) | ||
for name, test in tests.items(): | ||
del test["id"] | ||
del test["scan_id"] | ||
del test["site_id"] | ||
del test["name"] | ||
test["score_description"] = get_score_description(test["result"]) | ||
tests[name] = {**test.pop("output"), **test} | ||
|
||
else: | ||
hidden = request.form.get("hidden", "false") == "true" | ||
|
||
scan = database.insert_scan(site_id, hidden=hidden) | ||
scan_id = scan["id"] | ||
|
||
# Get the site's cookies and headers | ||
# TODO: add API to insert these into the db | ||
# headers = database.select_site_headers(hostname) | ||
|
||
try: | ||
result = scanner.scan(hostname) | ||
scan = result["scan"] | ||
tests = result["tests"] | ||
|
||
if "error" in result: | ||
scan = database.update_scan_state(scan_id, STATE_FAILED, error=result["error"]) | ||
else: | ||
scan = database.insert_test_results( | ||
site_id, | ||
scan_id, | ||
result, | ||
) | ||
except: | ||
# If we are unsuccessful, close out the scan in the database | ||
scan = database.update_scan_state(scan_id, STATE_FAILED) | ||
|
||
# Print the exception to stderr if we're in dev | ||
if DEVELOPMENT_MODE: | ||
import traceback | ||
|
||
print("Error detected in scan for: " + hostname) | ||
traceback.print_exc(file=sys.stderr) | ||
|
||
scan["start_time"] = scan["start_time"].isoformat() | ||
scan["end_time"] = scan["end_time"].isoformat() | ||
|
||
history = database.select_scan_host_history(site_id) | ||
|
||
# Prune for when the score doesn't change; thanks to chuck for the elegant list comprehension | ||
history = [ | ||
{ | ||
"end_time": v["end_time"].isoformat(), | ||
"grade": v["grade"], | ||
"id": v["scan_id"], | ||
"score": v["score"], | ||
} | ||
for k, v in enumerate(history) | ||
if history[k].get('score') is not history[k - 1].get('score') or k == 0 | ||
] | ||
|
||
return ( | ||
jsonify( | ||
{ | ||
"scan": scan, | ||
"tests": tests, | ||
"history": history, | ||
} | ||
), | ||
status_code, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,32 @@ | ||
import sys | ||
|
||
from flask import Flask | ||
|
||
from httpobs.conf import API_PORT, API_PROPAGATE_EXCEPTIONS, DEVELOPMENT_MODE | ||
from httpobs.website import add_response_headers | ||
from httpobs.website.api import api | ||
from httpobs.website.monitoring import monitoring_api | ||
|
||
|
||
def __exit_with(msg: str) -> None: | ||
print(msg) | ||
sys.exit(1) | ||
|
||
def create_app(): | ||
# Register the application with flask | ||
app = Flask('http-observatory') | ||
app.config['PROPAGATE_EXCEPTIONS'] = API_PROPAGATE_EXCEPTIONS | ||
|
||
# Register the application with flask | ||
app = Flask('http-observatory') | ||
app.config['PROPAGATE_EXCEPTIONS'] = API_PROPAGATE_EXCEPTIONS | ||
app.register_blueprint(api) | ||
app.register_blueprint(monitoring_api) | ||
from httpobs.website.api import api | ||
from httpobs.website.api_v2 import api_v2 | ||
from httpobs.website.monitoring import monitoring_api | ||
|
||
app.register_blueprint(api) | ||
app.register_blueprint(api_v2, url_prefix="/api/v2") | ||
app.register_blueprint(monitoring_api) | ||
|
||
@app.route('/') | ||
@add_response_headers() | ||
def main() -> str: | ||
return 'Welcome to the HTTP Observatory!' | ||
return app | ||
|
||
|
||
def run(): | ||
app = create_app() | ||
app.run(debug=DEVELOPMENT_MODE, port=API_PORT) | ||
|
||
|
||
if __name__ == '__main__': | ||
run() | ||
|
||
# make backwards compatible with uwsgi setup | ||
# TODO: move into wsgi.py | ||
app = create_app() |