· 7 years ago · Jan 17, 2018, 03:02 PM
1"""Backend of API Explorer
2
3Serves as a proxy between client side code and API server
4"""
5
6from api_explorer_oauth_client import APIExplorerOAuthClient
7
8import cgi
9import json
10import logging
11import sys
12
13import flask
14import oauth
15import werkzeug.debug
16
17import api_explorer_oauth_client
18import js_version
19try:
20 import secrets
21except ImportError:
22 # If the secrets aren't present, we can't run the server.
23 logging.critical("Can't find secrets.py.\nCopy secrets.example.py" +
24 " to secrets.py, enter the necessary values, and try again.")
25 sys.exit(1)
26
27app = flask.Flask(__name__)
28app.config.from_object('explorer.settings')
29
30if app.debug:
31 app.wsgi_app = werkzeug.debug.DebuggedApplication(app.wsgi_app,
32 evalex=True)
33
34# Keep around an instance of the client. It's reusable because all the
35# stateful stuff is passed around as parameters.
36OAuthClient = api_explorer_oauth_client.APIExplorerOAuthClient(
37 secrets.server_url, secrets.consumer_key, secrets.consumer_secret)
38
39
40@app.route("/")
41def index():
42 return flask.render_template("index.html",
43 prod=(not app.debug),
44 js_version=js_version.SHASUM,
45 is_logged_in=is_logged_in())
46
47
48@app.route("/group/<path:group>")
49def group_url(group):
50 if not flask.request.is_xhr:
51 return index()
52 else:
53 return "Invalid request", 400
54
55
56@app.route("/api/v1/<path:method>")
57def api_proxy(method):
58 # Relies on X-Requested-With header
59 # http://flask.pocoo.org/docs/api/#flask.Request.is_xhr
60 url_template = "api/v1/{0}"
61 if flask.request.is_xhr:
62
63 resource = OAuthClient.access_api_resource(
64 url_template.format(method), access_token(),
65 query_params=flask.request.args.items(),
66 method=flask.request.method)
67
68 response_text = resource.text
69 if "text/html" in resource.headers["Content-Type"]:
70 # per this stackoverflow thread
71 # http://stackoverflow.com/questions/1061697/whats-the-easiest-way-to-escape-html-in-python
72 response_text = cgi.escape(response_text).encode("ascii",
73 "xmlcharrefreplace")
74
75 # Include the original headers with response body.
76 # The client side will know what to do with these.
77 # There is a limit of 498 bytes per header, which will not be changed
78 # https://code.google.com/p/googleappengine/issues/detail?id=407
79 response = flask.make_response(json.dumps({
80 "headers": dict(resource.headers),
81 "response": response_text}))
82
83 response.headers["X-Original-Status"] = resource.status_code
84 response.headers["Content-Type"] = resource.headers["Content-Type"]
85
86 return response
87 else:
88 return index()
89
90
91# Begin the process of getting a request token from Khan.
92@app.route("/oauth_get_request_token")
93def oauth_get_request_token():
94 request_token_url = OAuthClient.url_for_request_token(
95 flask.url_for("oauth_callback",
96 continuation=flask.request.args.get("continue"),
97 _external=True))
98
99 logging.debug("Redirecting to request token URL: \n{0}".format(
100 request_token_url))
101 return flask.redirect(request_token_url)
102
103
104# The OAuth approval flow finishes here.
105# Query string version would have been preferable though it causes oauth
106# signature errors.
107@app.route("/oauth_callback")
108@app.route("/oauth_callback/<path:continuation>")
109def oauth_callback(continuation=None):
110 oauth_token = flask.request.args.get("oauth_token", "")
111 oauth_secret = flask.request.args.get("oauth_token_secret", "")
112 oauth_verifier = flask.request.args.get("oauth_verifier", "")
113
114 request_token = oauth.OAuthToken(oauth_token, oauth_secret)
115 request_token.set_verifier(oauth_verifier)
116
117 flask.session["request_token_string"] = request_token.to_string()
118
119 # We do this before we redirect so that there's no "limbo" state where the
120 # user has a request token but no access token.
121 access_token = OAuthClient.fetch_access_token(request_token)
122 flask.session["oauth_token_string"] = access_token.to_string()
123
124 # We're done authenticating, and the credentials are now stored in the
125 # session. We can redirect back home.
126 if continuation:
127 return flask.redirect(continuation)
128 else:
129 return flask.redirect(flask.url_for("index"))
130
131
132def access_token():
133 token_string = flask.session.get("oauth_token_string")
134
135 # Sanity check.
136 if not token_string:
137 clear_session()
138 return None
139
140 return oauth.OAuthToken.from_string(token_string)
141
142
143def is_logged_in():
144 return ("request_token_string" in flask.session and
145 "oauth_token_string" in flask.session)
146
147
148def clear_session():
149 flask.session.pop("request_token_string", None)
150 flask.session.pop("oauth_token_string", None)