Skip to content

Commit d98e753

Browse files
committed
Change sign-in behavior to use a popup for authorization flow.
Revise query parameter and credential management logic. Remove access token storage. Change-Id: I6abf51eb52d365d26206081670bb0ec0400c7f85
1 parent a32c738 commit d98e753

File tree

11 files changed

+137
-86
lines changed

11 files changed

+137
-86
lines changed

flask/02-sign-in/main.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def classroom_addon():
7272
"""
7373

7474
if "credentials" not in flask.session:
75-
return flask.render_template("authorization.html")
75+
return start_auth_flow()
7676

7777
return flask.render_template(
7878
"addon-discovery.html",
@@ -89,7 +89,7 @@ def test_api_request(request_type="username"):
8989
"""
9090

9191
if "credentials" not in flask.session:
92-
return flask.render_template("authorization.html")
92+
return start_auth_flow()
9393

9494
# Load credentials from the session.
9595
credentials = google.oauth2.credentials.Credentials(
@@ -114,10 +114,9 @@ def test_api_request(request_type="username"):
114114
flask.session["credentials"] = credentials_to_dict(credentials)
115115

116116
# Render the results of the API call.
117-
return flask.render_template(
118-
"show-api-query-result.html",
119-
data=json.dumps(fetched_data, indent=2),
120-
data_title=request_type)
117+
return flask.render_template("show-api-query-result.html",
118+
data=json.dumps(fetched_data, indent=2),
119+
data_title=request_type)
121120

122121

123122
@app.route("/authorize")
@@ -208,12 +207,22 @@ def revoke():
208207

209208
status_code = getattr(revoke, "status_code")
210209
if status_code == 200:
211-
return flask.render_template("authorization.html")
210+
return start_auth_flow()
212211
else:
213212
return flask.render_template(
214213
"index.html", message="An error occurred during revocation!")
215214

216215

216+
@app.route("/start-auth-flow")
217+
def start_auth_flow():
218+
"""
219+
Starts the OAuth 2.0 authorization flow. It's important that the
220+
template be rendered to properly manage popups.
221+
"""
222+
223+
return flask.render_template("authorization.html")
224+
225+
217226
@app.route("/clear")
218227
def clear_credentials():
219228
"""

flask/02-sign-in/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Flask==2.0.2
2-
google_api_python_client==2.31.0
2+
google_api_python_client==2.33.0
33
google_auth_oauthlib==0.4.6
44
protobuf==3.19.1
55
requests==2.26.0

flask/02-sign-in/templates/authorization.html

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,20 @@
1515
-->
1616

1717
{% extends "base.html"%}
18+
19+
{% block headcontent %}
20+
<script>
21+
var popupWindow;
22+
23+
function createPopup(url) {
24+
popupWindow = window.open(url, 'popUpWindow',
25+
'height=500,width=500,left=100,top=100,resizable=no,' +
26+
'scrollbars=yes,toolbar=yes,menubar=no,location=yes,' +
27+
'directories=no,status=yes');
28+
}
29+
</script>
30+
{% endblock %}
31+
1832
{% block content %}
1933
<div class="container">
2034
<h2>Authorization Required</h2>
@@ -23,15 +37,12 @@ <h2>Authorization Required</h2>
2337
Developer note: if you have *never* seen the user before, they'll need to
2438
authorize your app. It's most reliable to do so in a popup due to add-ons
2539
being presented in an iframe.
26-
Note that this link also calls a function to close the iframe; since we
27-
won't know when the user has finished the authorization flow, we won't
28-
be able to reload the addon with their correct credentials. -->
29-
<a target="popup" onclick="closeAddonIframe()" href="{{ url_for('authorize') }}">
40+
-->
41+
<a target="popup" onclick="createPopup(this.href);return false" href="{{ url_for('authorize') }}">
3042
<img src="../static/btn_google_signin_dark_normal_web.png" alt="Authorize the user">
3143
</a>
3244
<br>This will immediately start the authorization flow in a popup.
3345
<br>This should only be necessary once per user, unless your app has changed scopes or they
3446
have revoked their credentials.
35-
<br>Once complete, close the popup and use the other links below.
3647
</div>
3748
{% endblock %}

flask/02-sign-in/templates/close-me.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
-->
1616

1717
{% extends "base.html"%}
18-
{% block content %}
19-
<div class="container">
20-
<h2>Authorization Complete!</h2>
21-
<hr>
22-
The OAuth2 authorization flow has completed. You may now close this window.
23-
</div>
18+
19+
{% block headcontent %}
20+
<script>
21+
window.opener.location.href = "{{ url_for('classroom_addon') }}";
22+
window.close();
23+
</script>
2424
{% endblock %}

flask/02-sign-in/templates/navbar.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
<a style="background-color: #db4437" href="{{url_for('clear_credentials')}}">Logout
3434
{{ session['username'] }}</a>
3535
{% else %}
36-
<a style="background-color: #4285f4" href="{{url_for('authorize')}}">Login</a>
36+
<a style="background-color: #4285f4" href="{{url_for('start_auth_flow')}}">Login</a>
3737
{% endif %}
3838
</li>
3939
</ul>

flask/02-sign-in/templates/signed-out.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616

1717
{% extends "base.html"%}
1818
{% block headcontent %}
19-
<meta http-equiv="refresh" content="3;url={{url_for('classroom_addon')}}" />
19+
<meta http-equiv="refresh" content="3;url={{url_for('start_auth_flow')}}" />
2020
{% endblock %}
2121

2222
{% block content %}
2323
<div class="container">
2424
<h2>Sign-out Complete!</h2>
2525
<hr>
2626
Your credentials have been cleared; you are logged out.
27-
You will be redirected to the authorization page in 5 seconds.
27+
You will be redirected to the sign-in page in 5 seconds.
2828
</div>
2929
{% endblock %}

flask/03-query-parameters/main.py

Lines changed: 70 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
import os
1616
import flask
17-
from oauth2client import client
1817
import requests
1918
import json
2019
import flask_sqlalchemy
@@ -67,14 +66,13 @@ class User(db.Model):
6766
email = db.Column(db.String(120), unique=True)
6867
portrait_url = db.Column(db.Text())
6968

70-
# The user's credentials.
71-
# Note that refresh and access tokens will become invalid if:
69+
# The user's refresh token, which will be used to obtain an access token.
70+
# Note that refresh tokens will become invalid if:
7271
# - The refresh token has not been used for six months.
7372
# - The user revokes your app's access permissions.
7473
# - The user changes passwords.
7574
# - The user belongs to a Google Cloud Platform organization
7675
# that has session control policies in effect.
77-
access_token = db.Column(db.Text())
7876
refresh_token = db.Column(db.Text())
7977

8078
def __repr__(self):
@@ -119,46 +117,59 @@ def classroom_addon():
119117
db.create_all()
120118

121119
# Retrieve the login_hint and hd query parameters.
120+
login_hint = flask.request.args.get("login_hint")
121+
hd = flask.request.args.get("hd")
122+
122123
# It's possible that we might return to this route later, in which case the
123124
# parameters will not be passed in. Instead, use the values cached in the session.
124-
login_hint = flask.request.args.get("login_hint") or flask.session.get(
125-
"login_hint")
126-
hd = flask.request.args.get("hd") or flask.session.get("hd")
127125

128-
# Ensure that these parameters are cached in the session.
129-
if login_hint:
130-
flask.session["login_hint"] = login_hint
131-
if hd:
126+
# If neither query parameter is available, use the values in the session.
127+
if login_hint is None and hd is None:
128+
login_hint = flask.session.get("login_hint")
129+
hd = flask.session.get("hd")
130+
131+
# If there's no login_hint query parameter, then check for hd.
132+
# Send the user to the sign in page.
133+
elif hd is not None:
132134
flask.session["hd"] = hd
135+
return start_auth_flow()
136+
137+
# If the login_hint query parameter is available, we'll store it in the session.
138+
else:
139+
flask.session["login_hint"] = login_hint
133140

134-
# If we received a login_hint, we'll use it to check if we have any stored
135-
# credentials for this user.
136-
if login_hint:
137-
stored_credentials = get_credentials_from_storage(login_hint)
138-
139-
# If we have stored credentials, store them in the session.
140-
if stored_credentials:
141-
# Load the client secrets file contents.
142-
client_secrets_dict = json.load(
143-
open(CLIENT_SECRETS_FILE)).get("web")
144-
145-
# Set the credentials in the session.
146-
flask.session["credentials"] = {
147-
"token": stored_credentials.access_token,
148-
"refresh_token": stored_credentials.refresh_token,
149-
"token_uri": client_secrets_dict["token_uri"],
150-
"client_id": client_secrets_dict["client_id"],
151-
"client_secret": client_secrets_dict["client_secret"],
152-
"scopes": SCOPES
153-
}
154-
155-
# Set the username in the session.
156-
flask.session["username"] = stored_credentials.display_name
157-
158-
# Redirect to the authorization page if we received hd OR if we received
159-
# login_hint but don't have any stored credentials for this user.
160-
if "credentials" not in flask.session or hd:
161-
return flask.render_template("authorization.html")
141+
# Check if we have any stored credentials for this user.
142+
stored_credentials = get_credentials_from_storage(login_hint)
143+
144+
# If we have stored credentials, load them into the session.
145+
if stored_credentials:
146+
# Load the client secrets file contents.
147+
client_secrets_dict = json.load(open(CLIENT_SECRETS_FILE)).get("web")
148+
149+
# Update the credentials in the session.
150+
if not flask.session.get("credentials"):
151+
flask.session["credentials"] = {}
152+
153+
flask.session["credentials"]["token"] = flask.session.get("credentials").get("token") or None
154+
flask.session["credentials"][
155+
"refresh_token"] = stored_credentials.refresh_token
156+
flask.session["credentials"]["token_uri"] = client_secrets_dict[
157+
"token_uri"]
158+
flask.session["credentials"]["client_id"] = client_secrets_dict[
159+
"client_id"]
160+
flask.session["credentials"]["client_secret"] = client_secrets_dict[
161+
"client_secret"]
162+
flask.session["credentials"]["scopes"] = SCOPES
163+
164+
# Set the username in the session.
165+
flask.session["username"] = stored_credentials.display_name
166+
167+
# Redirect to the authorization page if we received login_hint but don't
168+
# have any stored credentials for this user. We need the refresh token
169+
# specifically.
170+
if "credentials" not in flask.session or \
171+
flask.session["credentials"]["refresh_token"] is None:
172+
return start_auth_flow()
162173

163174
return flask.render_template(
164175
"addon-discovery.html",
@@ -175,7 +186,7 @@ def test_api_request(request_type="username"):
175186
"""
176187

177188
if "credentials" not in flask.session:
178-
return flask.render_template("authorization.html")
189+
return start_auth_flow()
179190

180191
# Load credentials from the session and client id and client secret from file.
181192
credentials = google.oauth2.credentials.Credentials(
@@ -185,12 +196,12 @@ def test_api_request(request_type="username"):
185196
fetched_data = ""
186197

187198
if request_type == "username":
188-
if not flask.session.get("username"):
189-
user_info_service = googleapiclient.discovery.build(
190-
serviceName="oauth2", version="v2", credentials=credentials)
199+
# if not flask.session.get("username"):
200+
user_info_service = googleapiclient.discovery.build(
201+
serviceName="oauth2", version="v2", credentials=credentials)
191202

192-
flask.session["username"] = (
193-
user_info_service.userinfo().get().execute().get("name"))
203+
flask.session["username"] = (
204+
user_info_service.userinfo().get().execute().get("name"))
194205

195206
fetched_data = flask.session.get("username")
196207

@@ -308,10 +319,21 @@ def revoke():
308319

309320
status_code = getattr(revoke, "status_code")
310321
if status_code == 200:
311-
return flask.render_template("authorization.html")
322+
return start_auth_flow()
312323
else:
313324
return flask.render_template(
314-
"index.html", message="An error occurred during revocation!")
325+
"addon-discovery.html",
326+
message="An error occurred during revocation!")
327+
328+
329+
@app.route("/start-auth-flow")
330+
def start_auth_flow():
331+
"""
332+
Starts the OAuth 2.0 authorization flow. It's important that the
333+
template be rendered to properly manage popups.
334+
"""
335+
336+
return flask.render_template("authorization.html")
315337

316338

317339
@app.route("/clear")
@@ -377,16 +399,14 @@ def save_user_credentials(credentials=None, user_info=None):
377399
existing_user.email = user_info.get("email")
378400
existing_user.portrait_url = user_info.get("picture")
379401

380-
if credentials:
381-
existing_user.access_token = credentials.token
402+
if credentials and credentials.refresh_token is not None:
382403
existing_user.refresh_token = credentials.refresh_token
383404

384405
elif credentials and user_info:
385406
new_user = User(id=user_info.get("id"),
386407
display_name=user_info.get("name"),
387408
email=user_info.get("email"),
388409
portrait_url=user_info.get("picture"),
389-
access_token=credentials.token,
390410
refresh_token=credentials.refresh_token)
391411

392412
db.session.add(new_user)

flask/03-query-parameters/templates/authorization.html

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,20 @@
1515
-->
1616

1717
{% extends "base.html"%}
18+
19+
{% block headcontent %}
20+
<script>
21+
var popupWindow;
22+
23+
function createPopup(url) {
24+
popupWindow = window.open(url, 'popUpWindow',
25+
'height=500,width=500,left=100,top=100,resizable=no,' +
26+
'scrollbars=yes,toolbar=yes,menubar=no,location=yes,' +
27+
'directories=no,status=yes');
28+
}
29+
</script>
30+
{% endblock %}
31+
1832
{% block content %}
1933
<div class="container">
2034
<h2>Authorization Required</h2>
@@ -23,15 +37,12 @@ <h2>Authorization Required</h2>
2337
Developer note: if you have *never* seen the user before, they'll need to
2438
authorize your app. It's most reliable to do so in a popup due to add-ons
2539
being presented in an iframe.
26-
Note that this link also calls a function to close the iframe; since we
27-
won't know when the user has finished the authorization flow, we won't
28-
be able to reload the addon with their correct credentials. -->
29-
<a target="popup" onclick="closeAddonIframe()" href="{{ url_for('authorize') }}">
40+
-->
41+
<a target="popup" onclick="createPopup(this.href);return false" href="{{ url_for('authorize') }}">
3042
<img src="../static/btn_google_signin_dark_normal_web.png" alt="Authorize the user">
3143
</a>
3244
<br>This will immediately start the authorization flow in a popup.
3345
<br>This should only be necessary once per user, unless your app has changed scopes or they
3446
have revoked their credentials.
35-
<br>Once complete, close the popup and use the other links below.
3647
</div>
3748
{% endblock %}

flask/03-query-parameters/templates/close-me.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
-->
1616

1717
{% extends "base.html"%}
18-
{% block content %}
19-
<div class="container">
20-
<h2>Authorization Complete!</h2>
21-
<hr>
22-
The OAuth2 authorization flow has completed. You may now close this window.
23-
</div>
18+
19+
{% block headcontent %}
20+
<script>
21+
window.opener.location.href = "{{ url_for('classroom_addon') }}";
22+
window.close();
23+
</script>
2424
{% endblock %}

0 commit comments

Comments
 (0)