20210418 google sign in (#1275)
* Added Google Platform Library * Added Meta Element for Client ID * Added Google Sign-In Button * Authenticating the id_token on our backend * Saving id_token in flask session, using the id_token to fetch the current user and replaced the usages of AppEngine Users API (not from *_tests.py) * Correct the flow on pressing SignIn and SignOut * Code refactor * Added Comment for is_current_user_admin * Supporting GAE Users library for post request * Made some fixes * Changed Admin User condition * Reloading only on 200 response code * Do not require sign in and xsrf token while sending post request for login * Sign Out using Google Sign In if cookie is not set after login * Clearing the session if the id_token stored in the session variable becomes invalid or expires * Replaced GAE Users from tests * Replaced GAE users with framework users in tests.py
This commit is contained in:
Родитель
12ae9e0166
Коммит
8fbebb7989
|
@ -0,0 +1,51 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2021 Google Inc.
|
||||
#
|
||||
# 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 __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import logging
|
||||
|
||||
from google.oauth2 import id_token
|
||||
from google.auth.transport import requests
|
||||
from flask import session
|
||||
|
||||
|
||||
from framework import basehandlers
|
||||
# from framework import permissions
|
||||
# from framework import ramcache
|
||||
# from internals import models
|
||||
|
||||
class LoginAPI(basehandlers.APIHandler):
|
||||
"""Create a session using the id_token generated by Google Sign-In."""
|
||||
|
||||
def do_post(self):
|
||||
token = self.get_param('id_token')
|
||||
message = "Unable to Authenticate"
|
||||
|
||||
try:
|
||||
CLIENT_ID = '77756740465-e5r4o15qg4hkdfiucjpl231o79k3ipjv.apps.googleusercontent.com'
|
||||
idinfo = id_token.verify_oauth2_token(token, requests.Request(), CLIENT_ID)
|
||||
# userid = idinfo['sub']
|
||||
# email = idinfo['email']
|
||||
session["id_token"] = token
|
||||
message = "Done"
|
||||
# print(idinfo['email'], file=sys.stderr)
|
||||
except ValueError:
|
||||
message = "Invalid token"
|
||||
pass
|
||||
|
||||
return {'message': message}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2021 Google Inc.
|
||||
#
|
||||
# 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 __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import logging
|
||||
|
||||
from google.oauth2 import id_token
|
||||
from google.auth.transport import requests
|
||||
from flask import session
|
||||
|
||||
|
||||
from framework import basehandlers
|
||||
# from framework import permissions
|
||||
# from framework import ramcache
|
||||
# from internals import models
|
||||
|
||||
class LogoutAPI(basehandlers.APIHandler):
|
||||
"""Create a session using the id_token generated by Google Sign-In."""
|
||||
|
||||
def do_post(self):
|
||||
session.clear()
|
||||
return {'message': 'Done'}
|
||||
|
|
@ -22,7 +22,8 @@ import datetime
|
|||
import json
|
||||
import logging
|
||||
|
||||
from google.appengine.api import users
|
||||
# from google.appengine.api import users
|
||||
from framework import users
|
||||
|
||||
from framework import basehandlers
|
||||
from internals import models
|
||||
|
|
|
@ -22,7 +22,8 @@ import datetime
|
|||
import mock
|
||||
import flask
|
||||
|
||||
from google.appengine.api import users
|
||||
# from google.appengine.api import users
|
||||
from framework import users
|
||||
|
||||
from api import metricsdata
|
||||
from internals import models
|
||||
|
|
|
@ -24,6 +24,9 @@ from api import stars_api
|
|||
from api import token_refresh_api
|
||||
from framework import basehandlers
|
||||
|
||||
from api import login_api
|
||||
from api import logout_api
|
||||
|
||||
|
||||
# TODO(jrobbins): Advance this to v1 once we have it fleshed out
|
||||
API_BASE = '/api/v0'
|
||||
|
@ -34,7 +37,8 @@ app = basehandlers.FlaskApplication([
|
|||
('/features/<int:feature_id>/approvals/<int:field_id>',
|
||||
approvals_api.ApprovalsAPI),
|
||||
# ('/features/<int:feature_id>/approvals/<int:field_id>/comments', TODO),
|
||||
|
||||
('/login', login_api.LoginAPI),
|
||||
('/logout', logout_api.LogoutAPI),
|
||||
('/currentuser/stars', stars_api.StarsAPI),
|
||||
('/currentuser/cues', cues_api.CuesAPI),
|
||||
('/currentuser/token', token_refresh_api.TokenRefreshAPI),
|
||||
|
|
|
@ -4,8 +4,8 @@ from __future__ import print_function
|
|||
import datetime
|
||||
|
||||
from google.appengine.ext import db
|
||||
from google.appengine.api import users
|
||||
|
||||
# from google.appengine.api import users
|
||||
from framework import users
|
||||
|
||||
def email_to_list():
|
||||
def wrapper(value):
|
||||
|
|
|
@ -25,7 +25,7 @@ import flask
|
|||
import flask.views
|
||||
import werkzeug.exceptions
|
||||
|
||||
from google.appengine.api import users
|
||||
from google.appengine.api import users as gae_users
|
||||
from google.appengine.ext import db
|
||||
|
||||
import settings
|
||||
|
@ -39,6 +39,12 @@ from internals import models
|
|||
from django.template.loader import render_to_string
|
||||
import django
|
||||
|
||||
from google.oauth2 import id_token
|
||||
from google.auth.transport import requests
|
||||
from flask import session
|
||||
import sys
|
||||
from framework import users
|
||||
|
||||
# Initialize django so that it'll function when run as a standalone script.
|
||||
# https://django.readthedocs.io/en/latest/releases/1.7.html#standalone-scripts
|
||||
django.setup()
|
||||
|
@ -71,10 +77,16 @@ class BaseHandler(flask.views.MethodView):
|
|||
|
||||
def get_current_user(self, required=False):
|
||||
# TODO(jrobbins): oauth support
|
||||
user = users.get_current_user()
|
||||
if required and not user:
|
||||
current_user = None
|
||||
if self.request.method == 'POST':
|
||||
current_user = users.get_current_user() or gae_users.get_current_user()
|
||||
else:
|
||||
current_user = users.get_current_user()
|
||||
|
||||
|
||||
if required and not current_user:
|
||||
self.abort(403, msg='User must be signed in')
|
||||
return user
|
||||
return current_user
|
||||
|
||||
def get_param(
|
||||
self, name, default=None, required=True, validator=None, allowed=None):
|
||||
|
@ -149,7 +161,10 @@ class APIHandler(BaseHandler):
|
|||
|
||||
def post(self, *args, **kwargs):
|
||||
"""Handle an incoming HTTP POST request."""
|
||||
self.require_signed_in_and_xsrf_token()
|
||||
is_login_request = str(self.request.url_rule) == '/api/v0/login'
|
||||
|
||||
if not is_login_request:
|
||||
self.require_signed_in_and_xsrf_token()
|
||||
headers = self.get_headers()
|
||||
ramcache.check_for_distributed_invalidation()
|
||||
handler_data = self.do_post(*args, **kwargs)
|
||||
|
@ -286,7 +301,7 @@ class FlaskHandler(BaseHandler):
|
|||
if user:
|
||||
user_pref = models.UserPref.get_signed_in_user_pref()
|
||||
common_data['login'] = (
|
||||
'Sign out', users.create_logout_url(dest_url=current_path))
|
||||
'Sign out', "SignOut")
|
||||
common_data['user'] = {
|
||||
'can_create_feature': permissions.can_create_feature(user),
|
||||
'can_edit': permissions.can_edit_any_feature(user),
|
||||
|
@ -299,7 +314,7 @@ class FlaskHandler(BaseHandler):
|
|||
else:
|
||||
common_data['user'] = None
|
||||
common_data['login'] = (
|
||||
'Sign in', users.create_login_url(dest_url=current_path))
|
||||
'Sign in', "Sign In")
|
||||
common_data['xsrf_token'] = xsrf.generate_token(None)
|
||||
common_data['xsrf_token_expires'] = 0
|
||||
return common_data
|
||||
|
|
|
@ -24,7 +24,8 @@ import flask
|
|||
import flask.views
|
||||
import werkzeug.exceptions # Flask HTTP stuff.
|
||||
|
||||
from google.appengine.api import users
|
||||
# from google.appengine.api import users
|
||||
from framework import users
|
||||
|
||||
from framework import basehandlers
|
||||
from framework import xsrf
|
||||
|
|
|
@ -19,16 +19,15 @@ from __future__ import print_function
|
|||
import logging
|
||||
import flask
|
||||
|
||||
from google.appengine.api import users
|
||||
|
||||
from google.appengine.api import users as gae_users
|
||||
from framework import users
|
||||
from internals import models
|
||||
|
||||
|
||||
def can_admin_site(user):
|
||||
"""Return True if the current user is allowed to administer the site."""
|
||||
# A user is an admin if they are an admin of the GAE project.
|
||||
# TODO(jrobbins): delete this statement after legacy admins moved to AppUser.
|
||||
if users.is_current_user_admin():
|
||||
if gae_users.is_current_user_admin():
|
||||
return True
|
||||
|
||||
# A user is an admin if they have an AppUser entity that has is_admin set.
|
||||
|
@ -99,7 +98,7 @@ def _reject_or_proceed(
|
|||
|
||||
# Give the user a chance to sign in
|
||||
if not user and req.method == 'GET':
|
||||
return handler_obj.redirect(users.create_login_url(req.full_path))
|
||||
return handler_obj.redirect('/features?loginStatus=False')
|
||||
|
||||
if not perm_function(user):
|
||||
handler_obj.abort(403)
|
||||
|
|
|
@ -21,7 +21,8 @@ import testing_config # Must be imported before the module under test.
|
|||
import mock
|
||||
import werkzeug.exceptions # Flask HTTP stuff.
|
||||
|
||||
from google.appengine.api import users
|
||||
# from google.appengine.api import users
|
||||
from framework import users
|
||||
|
||||
from framework import basehandlers
|
||||
from framework import permissions
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
import sys
|
||||
from flask import session
|
||||
from google.oauth2 import id_token
|
||||
from google.auth.transport import requests
|
||||
|
||||
class User(object):
|
||||
"""Provides the email address, nickname, and ID for a user.
|
||||
|
||||
A nickname is a human-readable string that uniquely identifies a Google user,
|
||||
akin to a username. For some users, this nickname is an email address, but for
|
||||
other users, a different nickname is used.
|
||||
|
||||
A user is a Google Accounts user.
|
||||
|
||||
`federated_identity` and `federated_provider` are decommissioned and should
|
||||
not be used.
|
||||
|
||||
This class is based on google.appengine.api.users.User class
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
__user_id = None
|
||||
__federated_identity = None
|
||||
__federated_provider = None
|
||||
|
||||
def __init__(self, email=None, _auth_domain=None,
|
||||
_user_id=None, federated_identity=None, federated_provider=None,
|
||||
_strict_mode=True):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
email: An optional string of the user's email address. It defaults to
|
||||
the current user's email address.
|
||||
federated_identity: Decommissioned, don't use.
|
||||
federated_provider: Decommissioned, don't use.
|
||||
|
||||
Raises:
|
||||
UserNotFoundError: If the user is not logged in and both `email` and
|
||||
`federated_identity` are empty.
|
||||
"""
|
||||
|
||||
self.__email = email
|
||||
self.__federated_identity = federated_identity
|
||||
self.__federated_provider = federated_provider
|
||||
self.__auth_domain = _auth_domain
|
||||
self.__user_id = _user_id or None
|
||||
|
||||
|
||||
def nickname(self):
|
||||
"""Returns the user's nickname.
|
||||
|
||||
The nickname will be a unique, human readable identifier for this user with
|
||||
respect to this application. It will be an email address for some users,
|
||||
and part of the email address for some users.
|
||||
|
||||
Returns:
|
||||
The nickname of the user as a string.
|
||||
"""
|
||||
if (self.__email and self.__auth_domain and
|
||||
self.__email.endswith('@' + self.__auth_domain)):
|
||||
suffix_len = len(self.__auth_domain) + 1
|
||||
return self.__email[:-suffix_len]
|
||||
elif self.__federated_identity:
|
||||
return self.__federated_identity
|
||||
else:
|
||||
return self.__email
|
||||
|
||||
|
||||
def email(self):
|
||||
"""Returns the user's email address."""
|
||||
return self.__email
|
||||
|
||||
|
||||
def user_id(self):
|
||||
"""Obtains the user ID of the user.
|
||||
|
||||
Returns:
|
||||
A permanent unique identifying string or `None`. If the email address was
|
||||
set explicity, this will return `None`.
|
||||
"""
|
||||
return self.__user_id
|
||||
|
||||
|
||||
def auth_domain(self):
|
||||
"""Obtains the user's authentication domain.
|
||||
|
||||
Returns:
|
||||
A string containing the authentication domain. This method is internal and
|
||||
should not be used by client applications.
|
||||
"""
|
||||
return self.__auth_domain
|
||||
|
||||
|
||||
def federated_identity(self):
|
||||
"""Decommissioned, don't use.
|
||||
|
||||
Returns:
|
||||
A string containing the federated identity of the user. If the user is not
|
||||
a federated user, `None` is returned.
|
||||
"""
|
||||
return self.__federated_identity
|
||||
|
||||
|
||||
def federated_provider(self):
|
||||
"""Decommissioned, don't use.
|
||||
|
||||
Returns:
|
||||
A string containing the federated provider. If the user is not a federated
|
||||
user, `None` is returned.
|
||||
"""
|
||||
return self.__federated_provider
|
||||
|
||||
|
||||
def __unicode__(self):
|
||||
return six_subset.text_type(self.nickname())
|
||||
|
||||
def __str__(self):
|
||||
return str(self.nickname())
|
||||
|
||||
def __repr__(self):
|
||||
values = []
|
||||
if self.__email:
|
||||
values.append("email='%s'" % self.__email)
|
||||
if self.__federated_identity:
|
||||
values.append("federated_identity='%s'" % self.__federated_identity)
|
||||
if self.__user_id:
|
||||
values.append("_user_id='%s'" % self.__user_id)
|
||||
return 'users.User(%s)' % ','.join(values)
|
||||
|
||||
def __hash__(self):
|
||||
if self.__federated_identity:
|
||||
return hash((self.__federated_identity, self.__auth_domain))
|
||||
else:
|
||||
return hash((self.__email, self.__auth_domain))
|
||||
|
||||
def __cmp__(self, other):
|
||||
if not isinstance(other, User):
|
||||
return NotImplemented
|
||||
if self.__federated_identity:
|
||||
return cmp((self.__federated_identity, self.__auth_domain),
|
||||
(other.__federated_identity, other.__auth_domain))
|
||||
else:
|
||||
return cmp((self.__email, self.__auth_domain),
|
||||
(other.__email, other.__auth_domain))
|
||||
|
||||
def get_current_user():
|
||||
"""Retrieves information associated with the user that is making a request.
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
try:
|
||||
return User()
|
||||
except UserNotFoundError:
|
||||
return None
|
||||
|
||||
|
||||
def is_current_user_admin():
|
||||
"""Specifies whether the user making a request is an application admin.
|
||||
|
||||
Because administrator status is not persisted in the datastore,
|
||||
`is_current_user_admin()` is a separate function rather than a member function
|
||||
of the `User` class. The status only exists for the user making the current
|
||||
request.
|
||||
|
||||
Returns:
|
||||
`True` if the user is an administrator; all other user types return `False`.
|
||||
"""
|
||||
|
||||
# This environment variable was set by GAE based on a GAE session cookie.
|
||||
# With Google Sign-In, it will probably never be present. Hence, currently is always False
|
||||
# TODO (jrobbins): Implement this method
|
||||
return (os.environ.get('USER_IS_ADMIN', '0')) == '1'
|
||||
|
||||
|
||||
def get_current_user():
|
||||
|
||||
token = session.get('id_token')
|
||||
current_user = None
|
||||
if token:
|
||||
try:
|
||||
CLIENT_ID = '77756740465-e5r4o15qg4hkdfiucjpl231o79k3ipjv.apps.googleusercontent.com'
|
||||
idinfo = id_token.verify_oauth2_token(token, requests.Request(), CLIENT_ID)
|
||||
current_user = User(email=idinfo['email'], _user_id=idinfo['sub'])
|
||||
|
||||
except ValueError:
|
||||
# Remove the id_token from session if it is invalid or expired
|
||||
session.clear()
|
||||
current_user = None
|
||||
pass
|
||||
|
||||
return current_user
|
||||
|
||||
|
||||
def is_current_user_admin():
|
||||
return False
|
|
@ -23,7 +23,8 @@ import logging
|
|||
import time
|
||||
import traceback
|
||||
|
||||
from google.appengine.api import users
|
||||
# from google.appengine.api import users
|
||||
from framework import users
|
||||
|
||||
import settings
|
||||
|
||||
|
|
|
@ -28,7 +28,8 @@ from google.appengine.ext import db
|
|||
from google.appengine.api import mail
|
||||
from framework import ramcache
|
||||
import requests
|
||||
from google.appengine.api import users
|
||||
from google.appengine.api import users as gae_users
|
||||
from framework import users
|
||||
|
||||
from framework import cloud_tasks_helpers
|
||||
import settings
|
||||
|
@ -346,7 +347,7 @@ class DictModel(db.Model):
|
|||
output[key] = {'lat': value.lat, 'lon': value.lon}
|
||||
elif isinstance(value, db.Model):
|
||||
output[key] = to_dict(value)
|
||||
elif isinstance(value, users.User):
|
||||
elif isinstance(value, gae_users.User):
|
||||
output[key] = value.email()
|
||||
else:
|
||||
raise ValueError('cannot encode ' + repr(prop))
|
||||
|
|
|
@ -20,7 +20,8 @@ import testing_config # Must be imported before the module under test.
|
|||
|
||||
import mock
|
||||
from framework import ramcache
|
||||
from google.appengine.api import users
|
||||
# from google.appengine.api import users
|
||||
from framework import users
|
||||
|
||||
from internals import models
|
||||
|
||||
|
@ -154,13 +155,15 @@ class UserPrefTest(unittest.TestCase):
|
|||
self.user_pref_1.delete()
|
||||
self.user_pref_2.delete()
|
||||
|
||||
@mock.patch('google.appengine.api.users.get_current_user')
|
||||
# @mock.patch('google.appengine.api.users.get_current_user')
|
||||
@mock.patch('framework.users.get_current_user')
|
||||
def test_get_signed_in_user_pref__anon(self, mock_get_current_user):
|
||||
mock_get_current_user.return_value = None
|
||||
actual = models.UserPref.get_signed_in_user_pref()
|
||||
self.assertIsNone(actual)
|
||||
|
||||
@mock.patch('google.appengine.api.users.get_current_user')
|
||||
# @mock.patch('google.appengine.api.users.get_current_user')
|
||||
@mock.patch('framework.users.get_current_user')
|
||||
def test_get_signed_in_user_pref__first_time(self, mock_get_current_user):
|
||||
mock_get_current_user.return_value = testing_config.Blank(
|
||||
email=lambda: 'user1@example.com')
|
||||
|
@ -171,7 +174,8 @@ class UserPrefTest(unittest.TestCase):
|
|||
self.assertEqual(True, actual.notify_as_starrer)
|
||||
self.assertEqual(False, actual.bounced)
|
||||
|
||||
@mock.patch('google.appengine.api.users.get_current_user')
|
||||
# @mock.patch('google.appengine.api.users.get_current_user')
|
||||
@mock.patch('framework.users.get_current_user')
|
||||
def test_get_signed_in_user_pref__had_pref(self, mock_get_current_user):
|
||||
mock_get_current_user.return_value = testing_config.Blank(
|
||||
email=lambda: 'user2@example.com')
|
||||
|
@ -185,7 +189,8 @@ class UserPrefTest(unittest.TestCase):
|
|||
self.assertEqual(False, actual.notify_as_starrer)
|
||||
self.assertEqual(True, actual.bounced)
|
||||
|
||||
@mock.patch('google.appengine.api.users.get_current_user')
|
||||
# @mock.patch('google.appengine.api.users.get_current_user')
|
||||
@mock.patch('framework.users.get_current_user')
|
||||
def test_dismiss_cue(self, mock_get_current_user):
|
||||
"""We store the fact that a user has dismissed a cue card."""
|
||||
mock_get_current_user.return_value = testing_config.Blank(
|
||||
|
@ -197,7 +202,8 @@ class UserPrefTest(unittest.TestCase):
|
|||
self.assertEqual('one@example.com', revised_user_pref.email)
|
||||
self.assertEqual(['welcome-message'], revised_user_pref.dismissed_cues)
|
||||
|
||||
@mock.patch('google.appengine.api.users.get_current_user')
|
||||
# @mock.patch('google.appengine.api.users.get_current_user')
|
||||
@mock.patch('framework.users.get_current_user')
|
||||
def test_dismiss_cue__double(self, mock_get_current_user):
|
||||
"""We ignore the same user dismissing the same cue multiple times."""
|
||||
mock_get_current_user.return_value = testing_config.Blank(
|
||||
|
|
|
@ -29,7 +29,8 @@ from framework import ramcache
|
|||
from google.appengine.ext import db
|
||||
from google.appengine.api import mail
|
||||
import requests
|
||||
from google.appengine.api import users
|
||||
# from google.appengine.api import users
|
||||
from framework import users
|
||||
from google.appengine.ext.webapp.mail_handlers import BounceNotification
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
|
|
|
@ -26,7 +26,8 @@ import werkzeug.exceptions # Flask HTTP stuff.
|
|||
|
||||
from google.appengine.ext import db
|
||||
from google.appengine.api import mail
|
||||
from google.appengine.api import users
|
||||
# from google.appengine.api import users
|
||||
from framework import users
|
||||
|
||||
from internals import models
|
||||
from internals import notifier
|
||||
|
@ -39,8 +40,8 @@ class EmailFormattingTest(unittest.TestCase):
|
|||
self.feature_1 = models.Feature(
|
||||
name='feature one', summary='sum', category=1, visibility=1,
|
||||
standardization=1, web_dev_views=1, impl_status_chrome=1,
|
||||
created_by=users.User('creator@example.com'),
|
||||
updated_by=users.User('editor@example.com'),
|
||||
created_by=users.User(email='creator@example.com'),
|
||||
updated_by=users.User(email='editor@example.com'),
|
||||
blink_components=['Blink'])
|
||||
self.feature_1.put()
|
||||
self.component_1 = models.BlinkComponent(name='Blink')
|
||||
|
@ -58,8 +59,8 @@ class EmailFormattingTest(unittest.TestCase):
|
|||
self.feature_2 = models.Feature(
|
||||
name='feature two', summary='sum', category=1, visibility=1,
|
||||
standardization=1, web_dev_views=1, impl_status_chrome=1,
|
||||
created_by=users.User('creator@example.com'),
|
||||
updated_by=users.User('editor@example.com'),
|
||||
created_by=users.User(email='creator@example.com'),
|
||||
updated_by=users.User(email='editor@example.com'),
|
||||
blink_components=['Blink'])
|
||||
self.feature_2.put()
|
||||
|
||||
|
|
|
@ -26,7 +26,8 @@ from framework import utils
|
|||
from internals import models
|
||||
from framework import ramcache
|
||||
|
||||
from google.appengine.api import users
|
||||
# from google.appengine.api import users
|
||||
from framework import users
|
||||
|
||||
|
||||
class FeaturesJsonHandler(basehandlers.FlaskHandler):
|
||||
|
|
|
@ -27,7 +27,8 @@ from django import forms
|
|||
|
||||
# Appengine imports.
|
||||
from framework import ramcache
|
||||
from google.appengine.api import users
|
||||
# from google.appengine.api import users
|
||||
from framework import users
|
||||
from google.appengine.ext import db
|
||||
|
||||
from framework import basehandlers
|
||||
|
|
|
@ -21,7 +21,8 @@ from django import forms
|
|||
from django.core.validators import validate_email
|
||||
import string
|
||||
|
||||
from google.appengine.api import users
|
||||
# from google.appengine.api import users
|
||||
from framework import users
|
||||
|
||||
from internals import models
|
||||
from internals import processes
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
from google.appengine.api import users
|
||||
# from google.appengine.api import users
|
||||
from framework import users
|
||||
|
||||
from internals import models
|
||||
import settings
|
||||
|
|
|
@ -25,7 +25,8 @@ import os
|
|||
from framework import permissions
|
||||
from framework import ramcache
|
||||
import requests
|
||||
from google.appengine.api import users
|
||||
# from google.appengine.api import users
|
||||
from framework import users
|
||||
|
||||
from framework import basehandlers
|
||||
from internals import models
|
||||
|
|
|
@ -29,7 +29,8 @@ import time
|
|||
|
||||
from google.appengine.api import app_identity
|
||||
from google.appengine.api import memcache
|
||||
from google.appengine.api import users
|
||||
# from google.appengine.api import users
|
||||
from framework import users
|
||||
from google.appengine.ext import db
|
||||
from google.appengine.ext import webapp
|
||||
from google.appengine.ext.webapp.util import login_required
|
||||
|
|
|
@ -29,6 +29,7 @@ limitations under the License.
|
|||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="google-signin-client_id" content="77756740465-e5r4o15qg4hkdfiucjpl231o79k3ipjv.apps.googleusercontent.com">
|
||||
|
||||
<link rel="apple-touch-icon" href="/static/img/crstatus_128.png">
|
||||
<link rel="apple-touch-icon-precomposed" href="/static/img/crstatus_128.png">
|
||||
|
@ -57,6 +58,66 @@ limitations under the License.
|
|||
</style>
|
||||
|
||||
{% block css %}{% endblock %}
|
||||
|
||||
{# Google Platform Library for OAuth #}
|
||||
<script src="https://apis.google.com/js/platform.js?onload=onLoad" async defer></script>
|
||||
<script>
|
||||
|
||||
function onSignIn(googleUser) {
|
||||
var profile = googleUser.getBasicProfile();
|
||||
var id_token = googleUser.getAuthResponse().id_token;
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/api/v0/login');
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.onload = function() {
|
||||
if (xhr.status == 200) {
|
||||
window.location.replace(window.location.href.split("?")[0])
|
||||
}
|
||||
else {
|
||||
// signout if cookie not set
|
||||
signOut();
|
||||
}
|
||||
};
|
||||
let data = JSON.stringify({ "id_token": id_token})
|
||||
xhr.send(data);
|
||||
|
||||
}
|
||||
|
||||
function signOut() {
|
||||
var auth2 = gapi.auth2.getAuthInstance();
|
||||
auth2.signOut().then(function () {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/api/v0/logout');
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.onload = function() {
|
||||
if (xhr.status == 200) {
|
||||
console.log('Signed Out' + xhr.responseText);
|
||||
window.location.reload()
|
||||
}
|
||||
};
|
||||
// let data = JSON.stringify({ "id_token": id_token})
|
||||
xhr.send();
|
||||
console.log('User signed out.');
|
||||
});
|
||||
}
|
||||
|
||||
function onLoad() {
|
||||
gapi.load('auth2', function() {
|
||||
gapi.auth2.init();
|
||||
});
|
||||
}
|
||||
|
||||
function getQueryStringValue (key) {
|
||||
return decodeURIComponent(window.location.search.replace(new RegExp("^(?:.*[&\\?]" + encodeURIComponent(key).replace(/[\.\+\*]/g, "\\$&") + "(?:\\=([^&]*))?)?.*$", "i"), "$1"));
|
||||
}
|
||||
|
||||
if (getQueryStringValue("loginStatus") == 'False') {
|
||||
alert('Please log in.')
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
{# Loaded immediately because it is used by JS code on the page. #}
|
||||
<script>
|
||||
|
@ -122,6 +183,7 @@ limitations under the License.
|
|||
{% block js %}{% endblock %}
|
||||
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-179341418-1"></script>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
|
|
@ -30,12 +30,16 @@
|
|||
</a>
|
||||
<ul>
|
||||
<li><a href="/settings">Settings</a></li>
|
||||
<li><a href="{{login.1}}">{{login.0}}</a></li>
|
||||
<!-- <li><a href="{{login.1}}">{{login.0}}</a></li> -->
|
||||
<li><a href="#" onclick="signOut()">Sign out</a></li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<a href="{{login.1}}">{{login.0}}</a>
|
||||
<!-- <a href="{{login.1}}">{{login.0}}</a> -->
|
||||
<li><div class="g-signin2" data-onsuccess="onSignIn"></div></li>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</nav>
|
||||
</header>
|
||||
|
|
Загрузка…
Ссылка в новой задаче