diff --git a/appengine/flexible/endpoints/main.py b/appengine/flexible/endpoints/main.py index 18589640991..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 @@ -76,6 +77,13 @@ def auth_info_google_id_token(): return auth_info() +@app.route('/auth/info/firebase', methods=['GET']) +@cross_origin(send_wildcard=True) +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..59b3f8ba365 --- /dev/null +++ b/appengine/flexible/endpoints/main_test.py @@ -0,0 +1,91 @@ +# 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' + + +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 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"