Skip to content

Commit

Permalink
Mod20 fixes and added Mod21
Browse files Browse the repository at this point in the history
  • Loading branch information
wescpy committed Sep 21, 2022
1 parent 32fcf8c commit 0108eaf
Show file tree
Hide file tree
Showing 16 changed files with 595 additions and 8 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Module | Topic | Video | Codelab | START here | FINISH here
18|Add App Engine `taskqueue` pull tasks| _TBD_ | [link](https://codelabs.developers.google.com/codelabs/cloud-gae-python-migrate-18-gaepull?utm_source=codelabs&utm_medium=et&utm_campaign=CDR_wes_aap-serverless_mgrgaepull_sms_202013&utm_content=-) | Module 1 [code](/mod1-flask) (2.x) | Module 18 [code](/mod18-gaepull) (2.x)
19|Migrate to Cloud Pub/Sub| _TBD_ | [link](https://codelabs.developers.google.com/codelabs/cloud-gae-python-migrate-19-pubsub?utm_source=codelabs&utm_medium=et&utm_campaign=CDR_wes_aap-serverless_mgrpubsub_sms_202016&utm_content=-) | Module 18 [code](/mod18-gaepull) (2.x) | Module 19 [code](/mod19-pubsub) (2.x & 3.x)
20|Add App Engine `users` | _TBD_ | _TBD_ | Module 1 [code](/mod1-flask) (2.x) | Module 20 [code](/mod20-gaeusers) (2.x)
21|Migrate to Cloud Identity Platform | _TBD_ | _TBD_ | Module 20 [code](/mod20-gaeusers) (2.x) | _TBD_
21|Migrate to Cloud Identity Platform | _TBD_ | _TBD_ | Module 20 [code](/mod20-gaeusers) (2.x) | Module 21 [code](/mod21a-idenplat) (2.x) & [code](/mod21b-idenplat) (3.x)


### Table of contents
Expand Down
10 changes: 6 additions & 4 deletions mod20-gaeusers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

from flask import Flask, render_template, request
from google.appengine.api import app_identity, users
from google.appengine.api import users
from google.appengine.ext import ndb

app = Flask(__name__)
Expand Down Expand Up @@ -45,8 +45,10 @@ def root():
'who': user.nickname(),
'admin': '(admin)' if users.is_current_user_admin() else '',
'sign': 'Logout',
'link': '/_ah/logout?continue=https://%s/' % \
app_identity.get_default_version_hostname()
'link': '/_ah/logout?continue=%s://%s/' % (
request.environ['wsgi.url_scheme'],
request.environ['HTTP_HOST'],
), # alternative to users.create_logout_url()
} if user else { # not logged in
'who': 'user',
'admin': '',
Expand All @@ -55,5 +57,5 @@ def root():
}

# add visits to context and render template
context['visits'] = visits
context['visits'] = visits # display whether logged in or not
return render_template('index.html', **context)
6 changes: 3 additions & 3 deletions mod20-gaeusers/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
<head>
<title>VisitMe Example</title>
</head>

<body>
<p>
Welcome, {{ who }} <code>{{ admin }}</code> <button id="loginbtn">{{ sign }}</button>
Welcome, {{ who }} <code>{{ admin }}</code>
<button id="logbtn">{{ sign }}</button>
</p><hr>

<h1>VisitMe example</h1>
Expand All @@ -18,7 +18,7 @@ <h3>Last 10 visits</h3>
</ul>

<script>
document.getElementById("loginbtn").onclick = () => {
document.getElementById("logbtn").onclick = () => {
window.location.href = '{{ link }}';
};
</script>
Expand Down
79 changes: 79 additions & 0 deletions mod21a-idenplat/.gcloudignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# This file specifies files that are *not* uploaded to Google Cloud Platform
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
# $ gcloud topic gcloudignore
#
.gcloudignore

# Source code control files
.git/
.gitignore
.hgignore
.hg/

# README/text files
LICENSE
*.md

# Tests/results (not in .gitignore)
noxfile.py
pylintrc
pylintrc.test

# most of .gitignore (except `lib`)
#
# Python
*.py[cod]
__pycache__/
/setup.cfg

# C extensions
*.so

# Packages
*.egg
*.egg-info
dist
build
eggs
.eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib64
*.tgz

# Installer logs
pip-log.txt

# Tests/results
.nox/
.pytest_cache/
.cache
.pytype
.coverage
coverage.xml
*sponge_log.xml
system_tests/local_test_setup

# Mac
.DS_Store

# IDEs/editors
*.sw[op]
*~
.vscode
.idea

# Built documentation
docs/_build
docs.metadata

# Virtual environment
env/
3 changes: 3 additions & 0 deletions mod21a-idenplat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Module 21 - Migrate from App Engine `users` to Cloud Identity Platform

This repo folder is the corresponding Python 2 code to the _forthcoming_ Module 21 codelab. The tutorial STARTs with the Python 2 code in the [Module 20 repo folder](/mod20-gaeusers) and leads developers through a migration to Cloud Identity Platform, culminating in the code in this (`mod21a-idenplat`) folder. Also included is a migration from App Engine `ndb` to Google Cloud NDB, mirroring the content covered in [Module 2](http://g.co/codelabs/pae-migrate-cloudndb). There is also a Python 3 version of the app in the [Module 21b](/mod21b-idenplat) folder.
29 changes: 29 additions & 0 deletions mod21a-idenplat/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
script: main.app

libraries:
- name: grpcio
version: latest
- name: setuptools
version: latest
- name: ssl
version: latest
23 changes: 23 additions & 0 deletions mod21a-idenplat/appengine_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pkg_resources
from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)
# Add libraries to pkg_resources working set to find the distribution.
pkg_resources.working_set.add_entry(PATH)
78 changes: 78 additions & 0 deletions mod21a-idenplat/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from flask import Flask, render_template, request
from google.auth import default
from google.cloud import ndb
from googleapiclient import discovery
from firebase_admin import auth, initialize_app

def _get_gae_admins():
'return set of App Engine admins'
# setup constants for calling Cloud IAM Resource Manager
CREDS, PROJ_ID = default( # Application Default Credentials and project ID
['https://www.googleapis.com/auth/cloud-platform'])
IAM = discovery.build('cloudresourcemanager', 'v1', credentials=CREDS)
_TARGETS = frozenset(( # App Engine admin roles
'roles/viewer',
'roles/editor',
'roles/owner',
'roles/appengine.appAdmin',
))

# collate all users who are members of at least one GAE admin role (TARGETS)
admins = set() # set of all App Engine admins
allow_policy = IAM.projects().getIamPolicy(resource=PROJ_ID).execute()
for b in allow_policy['bindings']: # bindings in IAM allow policy
if b['role'] in _TARGETS: # only look at GAE admin roles
admins.update(user.split(':', 1)[1] for user in b['members'])
return admins

@app.route('/is_admin', methods=['POST'])
def is_admin():
'check if user (via their Firebase ID token) is GAE admin (POST) handler'
id_token = request.headers.get('Authorization')
email = auth.verify_id_token(id_token).get('email')
return {'admin': email in _ADMINS}, 200


# initialize Flask, Firebase, Cloud NDB; fetch set of App Engine admins
app = Flask(__name__)
initialize_app()
ds_client = ndb.Client()
_ADMINS = _get_gae_admins()


class Visit(ndb.Model):
'Visit entity registers visitor IP address & timestamp'
visitor = ndb.StringProperty()
timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
'create new Visit entity in Datastore'
with ds_client.context():
Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

def fetch_visits(limit):
'get most recent visits'
with ds_client.context():
return Visit.query().order(-Visit.timestamp).fetch(limit)


@app.route('/')
def root():
'main application (GET) handler'
store_visit(request.remote_addr, request.user_agent)
visits = fetch_visits(10)
return render_template('index.html', visits=visits)
13 changes: 13 additions & 0 deletions mod21a-idenplat/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
grpcio==1.0.0
protobuf<3.18.0
six>=1.13.0
flask
google-gax<0.13.0
google-api-core==1.31.1
google-api-python-client<=1.11.0
google-auth<2.0dev
google-cloud-datastore==1.15.3
google-cloud-firestore==1.9.0
google-cloud-ndb
google-cloud-pubsub==1.7.0
firebase-admin
87 changes: 87 additions & 0 deletions mod21a-idenplat/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<!doctype html>
<html>
<head>
<title>VisitMe Example</title>

<script type="module">
// import Firebase module attributes
import {
initializeApp
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-app.js";
import {
GoogleAuthProvider,
getAuth,
onAuthStateChanged,
signInWithPopup,
signOut
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-auth.js";

// Firebase config: at least 'apiKey' & 'authDomain' are required; go to
// console.firebase.google.com/project/PROJECT_ID/settings/general/web OR
// console.firebase.google.com/project/_/settings/general/web & pick project
var firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
};

// initialize Firebase app & auth components
initializeApp(firebaseConfig);
var auth = getAuth();
var provider = new GoogleAuthProvider();

// define login and logout button functions
function login() {
signInWithPopup(auth, provider);
};

function logout() {
signOut(auth);
};

// check if admin & switch to logout button on login; reset everything on logout
onAuthStateChanged(auth, async (user) => {
if (user && user != null) {
var email = user.email;
who.innerHTML = email;
logbtn.onclick = logout;
logbtn.innerHTML = "Logout";
var idToken = await user.getIdToken();
var rsp = await fetch("/is_admin", {
method: "POST",
headers: {Authorization: idToken}
});
var data = await rsp.json();
if (data.admin) {
admin.style.display = "inline";
}
} else {
who.innerHTML = "user";
admin.style.display = "none";
logbtn.onclick = login;
logbtn.innerHTML = "Login";
}
});
</script>
</head>

<body>
<p>
Welcome, <span id="who"></span> <span id="admin"><code>(admin)</code></span>
<button id="logbtn"></button>
</p><hr>

<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
<li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

<script>
var who = document.getElementById("who");
var admin = document.getElementById("admin");
var logbtn = document.getElementById("logbtn");
</script>
</body>
</html>
Loading

0 comments on commit 0108eaf

Please sign in to comment.