|
| 1 | +# Copyright 2022 Google LLC |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +from flask import Flask, render_template, request |
| 16 | +from google.auth import default |
| 17 | +from google.cloud import ndb |
| 18 | +from googleapiclient import discovery |
| 19 | +from firebase_admin import auth, initialize_app |
| 20 | + |
| 21 | +def _get_gae_admins(): |
| 22 | + 'return set of App Engine admins' |
| 23 | + # setup constants for calling Cloud IAM Resource Manager |
| 24 | + CREDS, PROJ_ID = default( # Application Default Credentials and project ID |
| 25 | + ['https://www.googleapis.com/auth/cloud-platform']) |
| 26 | + IAM = discovery.build('cloudresourcemanager', 'v1', credentials=CREDS) |
| 27 | + _TARGETS = frozenset(( # App Engine admin roles |
| 28 | + 'roles/viewer', |
| 29 | + 'roles/editor', |
| 30 | + 'roles/owner', |
| 31 | + 'roles/appengine.appAdmin', |
| 32 | + )) |
| 33 | + |
| 34 | + # collate all users who are members of at least one GAE admin role (TARGETS) |
| 35 | + admins = set() # set of all App Engine admins |
| 36 | + allow_policy = IAM.projects().getIamPolicy(resource=PROJ_ID).execute() |
| 37 | + for b in allow_policy['bindings']: # bindings in IAM allow policy |
| 38 | + if b['role'] in _TARGETS: # only look at GAE admin roles |
| 39 | + admins.update(user.split(':', 1)[1] for user in b['members']) |
| 40 | + return admins |
| 41 | + |
| 42 | +@app.route('/is_admin', methods=['POST']) |
| 43 | +def is_admin(): |
| 44 | + 'check if user (via their Firebase ID token) is GAE admin (POST) handler' |
| 45 | + id_token = request.headers.get('Authorization') |
| 46 | + email = auth.verify_id_token(id_token).get('email') |
| 47 | + return {'admin': email in _ADMINS}, 200 |
| 48 | + |
| 49 | + |
| 50 | +# initialize Flask, Firebase, Cloud NDB; fetch set of App Engine admins |
| 51 | +app = Flask(__name__) |
| 52 | +initialize_app() |
| 53 | +ds_client = ndb.Client() |
| 54 | +_ADMINS = _get_gae_admins() |
| 55 | + |
| 56 | + |
| 57 | +class Visit(ndb.Model): |
| 58 | + 'Visit entity registers visitor IP address & timestamp' |
| 59 | + visitor = ndb.StringProperty() |
| 60 | + timestamp = ndb.DateTimeProperty(auto_now_add=True) |
| 61 | + |
| 62 | +def store_visit(remote_addr, user_agent): |
| 63 | + 'create new Visit entity in Datastore' |
| 64 | + with ds_client.context(): |
| 65 | + Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put() |
| 66 | + |
| 67 | +def fetch_visits(limit): |
| 68 | + 'get most recent visits' |
| 69 | + with ds_client.context(): |
| 70 | + return Visit.query().order(-Visit.timestamp).fetch(limit) |
| 71 | + |
| 72 | + |
| 73 | +@app.route('/') |
| 74 | +def root(): |
| 75 | + 'main application (GET) handler' |
| 76 | + store_visit(request.remote_addr, request.user_agent) |
| 77 | + visits = fetch_visits(10) |
| 78 | + return render_template('index.html', visits=visits) |
0 commit comments