Skip to content

Commit ae4256a

Browse files
Merge branch 'py38-app-engine' of https://github.com/vpython/glowscript into py38-app-engine
2 parents 0cd708b + ba9efe3 commit ae4256a

File tree

3 files changed

+95
-29
lines changed

3 files changed

+95
-29
lines changed

ide/auth.py

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,56 @@
3131

3232
authNamespace = {} # we need to create the oauth object lazily, this is a "cache" that let's us build the oauth object only when needed.
3333

34+
def getSetting(name, default=None, returnObject=False):
35+
"""
36+
Grab some setting from the datastore. If it's not a string, it should be stored as a JSON representation.
37+
Set returnObject to True to force a JSON load. Otherwise use the type of the default to trigger JSON conversion
38+
"""
39+
app.logger.info("Getting setting for:" + str(name))
40+
result = models.Setting.get(name).value
41+
if result == 'NOT SET':
42+
result = default
43+
elif ((default is not None) and (type(default) != type(''))) or returnObject:
44+
# assume it's a JSON representation of an object, decode it and return that.
45+
try:
46+
result = json.loads(result)
47+
except json.decoder.JSONDecodeError:
48+
pass # maybe not?
49+
50+
app.logger.info("Got result:" + str(result))
51+
return result
52+
53+
def get_preview_users():
54+
"""
55+
Get the email addresses of users who are permitted to authenticate with a preview version of the app.
56+
"""
57+
return getSetting('preview_users', ['[email protected]','[email protected]'])
58+
59+
def get_preview_suffixes():
60+
"""
61+
Get the last domain parts of domains permitted for testing.
62+
"""
63+
return getSetting('preview_domain_suffixes',['appspot.com'])
64+
65+
def check_auth_host_for_preview(auth_host):
66+
"""
67+
Check to see of the current auth_host has one of the preview domains as a "suffix".
68+
"""
69+
70+
parts = auth_host.split('.')
71+
auth_host = '.'.join(parts[-2:]) # just keep last two components
72+
73+
for phost in get_preview_suffixes():
74+
if auth_host.endswith(phost):
75+
return True
76+
77+
return False
78+
3479
def get_project_name():
3580
"""
3681
Get the project name from the Datastore.
3782
"""
38-
web_setting = models.Setting.get('google_project_name')
39-
return web_setting.value
83+
return getSetting('google_project_name','glowscript')
4084

4185
def get_redirect_uri():
4286
return get_base_url() + '/google/auth' # get the valid redirect URL from the datastore setting
@@ -45,7 +89,7 @@ def get_base_url():
4589
"""
4690
Get the base_url from the datastore
4791
"""
48-
return models.Setting.get('auth_base_url').value
92+
return getSetting('auth_base_url')
4993
#
5094
# Robust way to check for running locally. Also easy to modify.
5195
#
@@ -175,8 +219,8 @@ def auth():
175219
token = oauth.google.authorize_access_token()
176220
user = oauth.google.parse_id_token(token)
177221

178-
if auth_host.endswith('uc.r.appspot.com'): # it's a test server, no sandbox!
179-
if user.get('email') not in ['[email protected]','[email protected]']: # only finish login for these guys
222+
if check_auth_host_for_preview(auth_host): # are we in a preview version?
223+
if user.get('email') not in get_preview_users(): # only finish login for these guys
180224
return redirect('/')
181225

182226
session['user'] = user

ide/routes.py

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,7 @@
2828
# python_version 2.7 works and can be deployed with Google App Engine Launcher 1.7.6
2929

3030
localport = '8080' # normally 8080
31-
website = 'glowscript' # normally glowscript
32-
33-
weblocs = ["www."+website+".org", website+".org", "localhost:"+localport,"127.0.0.1:"+localport,
34-
"glowscriptdev.spvi.net","uc.r.appspot.com","www.devbasherwo.org","devbasherwo.org"]
35-
36-
local_hosts = ['http://localhost','http://127.0.0.1']
31+
weblocs_safe = ["localhost:"+localport, "127.0.0.1:"+localport,] # only need these for local development
3732

3833
import json
3934
from io import BytesIO
@@ -44,6 +39,7 @@
4439
import flask
4540
import traceback
4641
import functools
42+
import urllib.parse
4743

4844
from google.cloud import ndb
4945
from google.auth.transport import requests
@@ -67,10 +63,12 @@ def chrange(b,e): return set(chr(x) for x in range(ord(b), ord(e)+1))
6763

6864
def ndb_wsgi_middleware(wsgi_app):
6965
"""
70-
This is helpful for Flask and DNB to play nice together.
66+
This is helpful for Flask and NDB to play nice together.
7167
7268
https://cloud.google.com/appengine/docs/standard/python3/migrating-to-cloud-ndb
73-
69+
70+
We need to be able to access NDB in the application context.
71+
If we're running a local datastore, make up a dummy project name.
7472
"""
7573

7674
project = emulator and 'glowscript-dev' or None
@@ -93,10 +91,30 @@ def middleware(environ, start_response):
9391

9492
return middleware
9593

94+
#
95+
# Now let's deal with the app
96+
#
97+
9698
from . import app, auth
9799

98100
app.wsgi_app = ndb_wsgi_middleware(app.wsgi_app) # Wrap the app in middleware.
99101

102+
#
103+
# set up logging
104+
#
105+
106+
import logging
107+
import google.cloud.logging # Don't conflict with standard logging
108+
from google.cloud.logging.handlers import CloudLoggingHandler
109+
110+
client = google.cloud.logging.Client()
111+
handler = CloudLoggingHandler(client)
112+
handler.setLevel(logging.INFO)
113+
app.logger.addHandler(handler)
114+
115+
def get_weblocs():
116+
return auth.getSetting('weblocs', weblocs_safe)
117+
100118
#
101119
# These next few routes are to serve static files in dev mode. GAE will handle these
102120
# in production
@@ -161,10 +179,9 @@ def idejs_static():
161179
ide_js = load_idejs()
162180

163181
host_name = get_auth_host_name()
164-
if host_name.endswith('uc.r.appspot.com'): # no sandbox for uc.r.appspot.com test instances
165-
# since we can't authenticate these instances anyway, no sanbox is needed
166-
ide_js = ide_js.replace('WEBSERVER_NAME_TEMPLATE',host_name)
167-
ide_js = ide_js.replace('SANDBOX_PREFIX_TEMPLATE','https://')
182+
if auth.check_auth_host_for_preview(host_name): # are we running in a preview?
183+
ide_js = ide_js.replace('WEBSERVER_NAME_TEMPLATE',host_name)
184+
ide_js = ide_js.replace('SANDBOX_PREFIX_TEMPLATE','https://') # no sandbox
168185
elif host_name.startswith('www.'):
169186
ide_js = ide_js.replace('WEBSERVER_NAME_TEMPLATE',host_name[4:])
170187
ide_js = ide_js.replace('SANDBOX_PREFIX_TEMPLATE','https://sandbox.')
@@ -196,14 +213,10 @@ def favicon_static():
196213
@app.route('/untrusted/<path:filename>')
197214
@no_cache
198215
def untrusted_static(filename):
199-
app.logger.info("serving untrusted")
200216
if filename == 'run.js':
201217
run_js = module_cache.get('run.js')
202218
if not run_js:
203-
app.logger.info("not found in cache")
204219
run_js = load_runjs()
205-
else:
206-
app.logger.info("found in cache")
207220

208221
host_name = get_auth_host_name()
209222
if host_name.startswith('sandbox.'):
@@ -239,21 +252,22 @@ def get_auth_host_name():
239252

240253
def trim_auth_host_name():
241254
#
242-
# if the host name has more than four parts, trim off the first part.
243-
# this allows appspot version to be tested that are not in production
244-
# e.g., 20201227t175543-dot-py38-glowscript.uc.r.appspot.com
255+
# if the host name has more than two parts, return the last two parts only.
256+
# This allows appspot version to be tested that are not in production
257+
# e.g., 20201227t175543-dot-glowscript.appspot.com
245258
#
259+
246260
host_header = get_auth_host_name()
247261
parts = host_header.split('.')
248-
host_header = '.'.join(parts[1:]) if len(parts)>4 else host_header
262+
host_header = '.'.join(parts[-2:]) if len(parts)>2 else host_header
249263
return host_header
250264

251265
def authorize_host():
252266
host_header = trim_auth_host_name()
253-
result = host_header in weblocs
267+
result = host_header in get_weblocs()
254268

255269
if not result:
256-
print("Host failed to authorize:", host_header)
270+
app.logger.info("Host failed to authorize:" + host_header + ":" + str(get_weblocs()))
257271

258272
return result
259273

@@ -328,7 +342,13 @@ def parseUrlPath(theRegexp, numGroups):
328342
for i in range(numGroups):
329343
value = m.group(i+1)
330344
if value:
331-
names[i] = value
345+
newValue = []
346+
for char in value:
347+
if char == '%' or char in unreserved:
348+
newValue.append(char)
349+
else:
350+
newValue.append(urllib.parse.quote(char))
351+
names[i] = ''.join(newValue)
332352
except:
333353
raise ParseUrlPathException('Parsing URL failed', 400)
334354

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ google-api-core==1.22.0
1111
google-api-python-client==1.10.0
1212
google-auth==1.20.0
1313
google-auth-httplib2==0.0.4
14-
google-cloud-core==1.3.0
14+
google-cloud-core==1.5.0
1515
google-cloud-datastore==1.13.2
16+
google-cloud-logging==2.0.2
1617
google-cloud-ndb==1.4.2
1718
google-cloud-secret-manager==1.0.0
1819
googleapis-common-protos==1.52.0
@@ -26,6 +27,7 @@ Jinja2==2.11.2
2627
lazy-object-proxy==1.4.3
2728
MarkupSafe==1.1.1
2829
mccabe==0.6.1
30+
proto-plus==1.13.0
2931
protobuf==3.12.4
3032
pyasn1==0.4.8
3133
pyasn1-modules==0.2.8

0 commit comments

Comments
 (0)