From b90dbe95ba8c6ec8a380ee5ec3b5b6330910fcf7 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 1 Jul 2016 12:51:12 -0700 Subject: [PATCH 1/2] Adding firebase auth to endpoints sample, fixes #396 Also adding test to the endpoint sample, which I somehow forgot to do. Change-Id: I506674a6289dbbb73f8b4ff906654f14948affcc --- appengine/flexible/endpoints/main.py | 6 ++ appengine/flexible/endpoints/main_test.py | 84 +++++++++++++++++++++++ appengine/flexible/endpoints/swagger.yaml | 31 +++++++-- 3 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 appengine/flexible/endpoints/main_test.py diff --git a/appengine/flexible/endpoints/main.py b/appengine/flexible/endpoints/main.py index 18589640991..da793d3865b 100644 --- a/appengine/flexible/endpoints/main.py +++ b/appengine/flexible/endpoints/main.py @@ -76,6 +76,12 @@ def auth_info_google_id_token(): return auth_info() +@app.route('/auth/info/firebase', methods=['GET']) +def auth_info_firebase(): + """Auth info with Firebase auth.""" + return auth_info() + + @app.errorhandler(http_client.INTERNAL_SERVER_ERROR) def unexpected_error(e): """Handle exceptions by returning swagger-compliant json.""" diff --git a/appengine/flexible/endpoints/main_test.py b/appengine/flexible/endpoints/main_test.py new file mode 100644 index 00000000000..f5ffd47332f --- /dev/null +++ b/appengine/flexible/endpoints/main_test.py @@ -0,0 +1,84 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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 base64 +import json +import os + +import main +import pytest + + +@pytest.fixture +def client(monkeypatch): + monkeypatch.chdir(os.path.dirname(main.__file__)) + main.app.testing = True + client = main.app.test_client() + return client + + +def test_index(client): + r = client.get('/') + assert r.status_code == 200 + + +def test_api_docs(client): + r = client.get('/api-docs') + assert r.status_code == 200 + + +def test_echo(client): + r = client.post( + '/echo', + data='{"message": "Hello"}', + headers={ + 'Content-Type': 'application/json' + }) + + assert r.status_code == 200 + data = json.loads(r.data.decode('utf-8')) + assert data['message'] == 'Hello' + + +def test_auth_info(client): + endpoints = [ + '/auth/info/googlejwt', + '/auth/info/googleidtoken', + '/auth/info/firebase'] + + encoded_info = base64.b64encode(json.dumps({ + 'id': '123' + }).encode('utf-8')) + + for endpoint in endpoints: + r = client.get( + endpoint, + headers={ + 'Content-Type': 'application/json' + }) + + assert r.status_code == 200 + data = json.loads(r.data.decode('utf-8')) + assert data['id'] == 'anonymous' + + r = client.get( + endpoint, + headers={ + 'Content-Type': 'application/json', + 'X-Endpoint-API-UserInfo': encoded_info + }) + + assert r.status_code == 200 + data = json.loads(r.data.decode('utf-8')) + assert data['id'] == '123' diff --git a/appengine/flexible/endpoints/swagger.yaml b/appengine/flexible/endpoints/swagger.yaml index 71bac9ce62f..8792ee42b20 100644 --- a/appengine/flexible/endpoints/swagger.yaml +++ b/appengine/flexible/endpoints/swagger.yaml @@ -30,6 +30,8 @@ paths: required: true schema: $ref: "#/definitions/echoMessage" + security: + - api_key: [] "/auth/info/googlejwt": get: description: "Returns the requests' authentication information." @@ -38,7 +40,7 @@ paths: - "application/json" responses: 200: - description: "Authenication info." + description: "Authentication info." schema: $ref: "#/definitions/authInfoResponse" x-security: @@ -55,7 +57,7 @@ paths: - "application/json" responses: 200: - description: "Authenication info." + description: "Authentication info." schema: $ref: "#/definitions/authInfoResponse" x-security: @@ -64,6 +66,21 @@ paths: # Your OAuth2 client's Client ID must be added here. You can add # multiple client IDs to accept tokens from multiple clients. - "YOUR-CLIENT-ID" + "/auth/info/firebase": + get: + description: "Returns the requests' authentication information." + operationId: "authInfoFirebase" + produces: + - "application/json" + responses: + 200: + description: "Authentication info." + schema: + $ref: "#/definitions/authInfoResponse" + x-security: + - firebase: + audiences: + - "YOUR-PROJECT-ID" definitions: echoMessage: properties: @@ -75,9 +92,6 @@ definitions: type: "string" email: type: "string" -# This section requires all requests to any path to require an API key. -security: -- api_key: [] securityDefinitions: # This section configures basic authentication with an API key. api_key: @@ -104,3 +118,10 @@ securityDefinitions: type: "oauth2" x-issuer: "accounts.google.com" x-jwks_uri: "https://www.googleapis.com/oauth2/v1/certs" + # This section configures authentication using Firebase Auth. + firebase: + authorizationUrl: "" + flow: "implicit" + type: "oauth2" + x-issuer: "https://securetoken.google.com/YOUR-PROJECT-ID" + x-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken@system.gserviceaccount.com" From 8c32a4b9b776aabb4ebe52be1e7178dea2187ad9 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 1 Jul 2016 13:19:55 -0700 Subject: [PATCH 2/2] Adding CORS Change-Id: If898855d17d2cb33d0883c926c9f461b051e7d1c --- appengine/flexible/endpoints/main.py | 2 ++ appengine/flexible/endpoints/main_test.py | 7 +++++++ appengine/flexible/endpoints/requirements.txt | 1 + 3 files changed, 10 insertions(+) diff --git a/appengine/flexible/endpoints/main.py b/appengine/flexible/endpoints/main.py index da793d3865b..86ba7ebfd37 100644 --- a/appengine/flexible/endpoints/main.py +++ b/appengine/flexible/endpoints/main.py @@ -23,6 +23,7 @@ import logging from flask import Flask, jsonify, request, send_from_directory +from flask_cors import cross_origin from six.moves import http_client import yaml @@ -77,6 +78,7 @@ def auth_info_google_id_token(): @app.route('/auth/info/firebase', methods=['GET']) +@cross_origin(send_wildcard=True) def auth_info_firebase(): """Auth info with Firebase auth.""" return auth_info() diff --git a/appengine/flexible/endpoints/main_test.py b/appengine/flexible/endpoints/main_test.py index f5ffd47332f..59b3f8ba365 100644 --- a/appengine/flexible/endpoints/main_test.py +++ b/appengine/flexible/endpoints/main_test.py @@ -82,3 +82,10 @@ def test_auth_info(client): assert r.status_code == 200 data = json.loads(r.data.decode('utf-8')) assert data['id'] == '123' + + +def test_cors(client): + r = client.options( + '/auth/info/firebase', headers={'Origin': 'example.com'}) + assert r.status_code == 200 + assert r.headers['Access-Control-Allow-Origin'] == '*' diff --git a/appengine/flexible/endpoints/requirements.txt b/appengine/flexible/endpoints/requirements.txt index ed947f4f419..65cbd791ff1 100644 --- a/appengine/flexible/endpoints/requirements.txt +++ b/appengine/flexible/endpoints/requirements.txt @@ -1,4 +1,5 @@ Flask==0.11.1 +flask-cors==2.1.2 gunicorn==19.6.0 gcloud==0.17.0 six==1.10.0