diff --git a/appengine/firebase-event-proxy/README.md b/appengine/firebase-event-proxy/README.md new file mode 100644 index 00000000000..eeb8b4ac107 --- /dev/null +++ b/appengine/firebase-event-proxy/README.md @@ -0,0 +1,49 @@ +# App Engine Firebase Event Proxy + +An example app that illustrates how to create a Java App Engine Standard Environment +app that proxies Firebase events to another App Engine app. + +# Java Firebase Event Proxy +Illustrates how to authenticate and subscribe to Firebase from Java App Engine. + +# Python App Engine Listener +Illustrates how to authenticate messages received from the proxy app. + +## Setup + +### Java Firebase Event Proxy +Firebase Secret +Put your Firebase secret in the file: +gae-firebase-event-proxy/src/main/webapp/firebase-secret.properties +``` +firebaseSecret= +``` + +* Billing must be enabled from Cloud console. +* Manual scaling should turned on and configured to 1 instance in appengine-web.xml + +## Running locally +### Java Firebase Event Proxy +``` +cd gae-firebase-event-proxy +mvn appengine:devserver +``` + +### Python App Engine Listener +``` +cd gae-firebase-listener-python +dev_appserver . +``` + +## Deploying + +### Java Firebase Event Proxy +``` +cd gae-firebase-event-proxy +mvn appengine:upload +``` + +### Python App Engine Listener +``` +appcfg.py -A -V v1 update gae-firebase-listener-python +``` diff --git a/appengine/firebase-event-proxy/gae-firebase-event-proxy/pom.xml b/appengine/firebase-event-proxy/gae-firebase-event-proxy/pom.xml new file mode 100644 index 00000000000..c4975f1f095 --- /dev/null +++ b/appengine/firebase-event-proxy/gae-firebase-event-proxy/pom.xml @@ -0,0 +1,156 @@ + + + + + 4.0.0 + war + 1.0-SNAPSHOT + + com.example.GaeFirebaseEventProxy + GaeFirebaseEventProxy + + + gae-firebase-event-proxy + 1 + 1.9.36 + 2.0.9.74.v20150814 + UTF-8 + true + + + + 3.1.0 + + + + + + com.google.appengine + appengine-api-1.0-sdk + ${appengine.version} + + + javax.servlet + servlet-api + 2.5 + provided + + + jstl + jstl + 1.2 + + + com.firebase + firebase-client-jvm + [1.0.8,) + + + com.fasterxml.jackson.core + jackson-core + 2.7.3 + + + com.firebase + firebase-token-generator + 2.0.0 + + + + + com.google.appengine + appengine-testing + ${appengine.version} + test + + + com.google.appengine + appengine-api-stubs + ${appengine.version} + test + + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + org.codehaus.mojo + versions-maven-plugin + 2.1 + + + compile + + display-dependency-updates + display-plugin-updates + + + + + + org.apache.maven.plugins + 3.1 + maven-compiler-plugin + + 1.7 + 1.7 + + + + org.apache.maven.plugins + maven-war-plugin + 2.4 + + true + + + + ${basedir}/src/main/webapp/WEB-INF + true + WEB-INF + + + + + + + com.google.appengine + appengine-maven-plugin + ${appengine.version} + + false + ${app.version} + + + + + + + + com.google.appengine + gcloud-maven-plugin + ${gcloud.plugin.version} + + true + + + + + diff --git a/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/java/com/example/GaeFirebaseEventProxy/FirebaseEventProxy.java b/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/java/com/example/GaeFirebaseEventProxy/FirebaseEventProxy.java new file mode 100644 index 00000000000..ffff83eaa34 --- /dev/null +++ b/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/java/com/example/GaeFirebaseEventProxy/FirebaseEventProxy.java @@ -0,0 +1,144 @@ +/* + * 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. + */ + +package com.example.GaeFirebaseEventProxy; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.logging.Logger; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.firebase.client.AuthData; +import com.firebase.client.DataSnapshot; +import com.firebase.client.Firebase; +import com.firebase.client.FirebaseError; +import com.firebase.client.ValueEventListener; +import com.firebase.security.token.TokenGenerator; +import com.google.appengine.api.utils.SystemProperty; + +public class FirebaseEventProxy { + + private static final Logger log = Logger.getLogger(FirebaseEventProxy.class.getName()); + + private String firebaseAuthToken; + + public FirebaseEventProxy() { + // Store Firebase authentication token as an instance variable. + this.firebaseAuthToken = this.getFirebaseAuthToken(this.getFirebaseSecret()); + } + + public void start() { + String FIREBASE_LOCATION = "https://gae-fb-proxy.firebaseio.com/"; + Firebase firebase = new Firebase(FIREBASE_LOCATION); + + // Authenticate with Firebase + firebase.authWithCustomToken(this.firebaseAuthToken, new Firebase.AuthResultHandler() { + @Override + public void onAuthenticationError(FirebaseError error) { + log.severe("Firebase login error: " + error.getMessage()); + } + + @Override + public void onAuthenticated(AuthData auth) { + log.info("Firebase login successful"); + } + }); + + // Subscribe to value events. Depending on use case, you may want to subscribe to child events + // through childEventListener. + firebase.addValueEventListener(new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot snapshot) { + if (snapshot.exists()) { + try { + // Convert value to JSON using Jackson + String json = new ObjectMapper().writeValueAsString(snapshot.getValue()); + + // Replace the URL with the url of your own listener app. + URL dest = new URL("http://gae-firebase-listener-python.appspot.com/log"); + HttpURLConnection connection = (HttpURLConnection) dest.openConnection(); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + + // Rely on X-Appengine-Inbound-Appid to authenticate. Turning off redirects is + // required to enable. + connection.setInstanceFollowRedirects(false); + + // Fill out header if in dev environment + if (SystemProperty.environment.value() != SystemProperty.Environment.Value.Production) { + connection.setRequestProperty("X-Appengine-Inbound-Appid", "dev-instance"); + } + + // Put Firebase data into http request + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("&fbSnapshot="); + stringBuilder.append(URLEncoder.encode(json, "UTF-8")); + connection.getOutputStream().write(stringBuilder.toString().getBytes()); + if (connection.getResponseCode() != 200) { + log.severe("Forwarding failed"); + } else { + log.info("Sent: " + json); + } + } catch (JsonProcessingException e) { + log.severe("Unable to convert Firebase response to JSON: " + e.getMessage()); + } catch (IOException e) { + log.severe("Error in connecting to app engine: " + e.getMessage()); + } + } + } + + @Override + public void onCancelled(FirebaseError error) { + log.severe("Firebase connection cancelled: " + error.getMessage()); + } + }); + } + + private String getFirebaseSecret() { + Properties props = new Properties(); + try { + // Read from src/main/webapp/firebase-secrets.properties + InputStream inputStream = new FileInputStream("firebase-secret.properties"); + props.load(inputStream); + return props.getProperty("firebaseSecret"); + } catch (java.net.MalformedURLException e) { + throw new RuntimeException( + "Error reading firebase secrets from file: src/main/webapp/firebase-sercrets.properties: " + + e.getMessage()); + } catch (IOException e) { + throw new RuntimeException( + "Error reading firebase secrets from file: src/main/webapp/firebase-sercrets.properties: " + + e.getMessage()); + } + } + + private String getFirebaseAuthToken(String firebaseSecret) { + Map authPayload = new HashMap(); + // uid and provider will have to match what you have in your firebase security rules + authPayload.put("uid", "gae-firebase-event-proxy"); + authPayload.put("provider", "com.example"); + TokenGenerator tokenGenerator = new TokenGenerator(firebaseSecret); + return tokenGenerator.createToken(authPayload); + } +} diff --git a/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/java/com/example/GaeFirebaseEventProxy/ServletContextListenerImpl.java b/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/java/com/example/GaeFirebaseEventProxy/ServletContextListenerImpl.java new file mode 100644 index 00000000000..b50dc4d3961 --- /dev/null +++ b/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/java/com/example/GaeFirebaseEventProxy/ServletContextListenerImpl.java @@ -0,0 +1,40 @@ +/* + * 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. + */ + +package com.example.GaeFirebaseEventProxy; + +import java.util.logging.Logger; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +// ServletContextListener that is called whenever your App Engine app starts up. +public class ServletContextListenerImpl implements ServletContextListener { + + private static final Logger log = Logger.getLogger(ServletContextListener.class.getName()); + + @Override + public void contextInitialized(ServletContextEvent event) { + log.info("Starting ...."); + FirebaseEventProxy proxy = new FirebaseEventProxy(); + proxy.start(); + } + + @Override + public void contextDestroyed(ServletContextEvent event) { + // App Engine does not currently invoke this method. + } +} diff --git a/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/webapp/WEB-INF/appengine-web.xml b/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..96aaab69ef4 --- /dev/null +++ b/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,27 @@ + + + + + ${app.id} + ${app.version} + true + + + 1 + + + + + + diff --git a/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/webapp/WEB-INF/logging.properties b/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/webapp/WEB-INF/logging.properties new file mode 100644 index 00000000000..e325f6cb079 --- /dev/null +++ b/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/webapp/WEB-INF/logging.properties @@ -0,0 +1,27 @@ +# 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. + +# A default java.util.logging configuration. +# (All App Engine logging is through java.util.logging by default). +# +# To use this configuration, copy it into your application's WEB-INF +# folder and add the following to your appengine-web.xml: +# +# +# +# +# + +# Set the default logging level for all loggers to WARNING +.level = INFO diff --git a/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/webapp/WEB-INF/web.xml b/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..b8ded3bcce1 --- /dev/null +++ b/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,26 @@ + + + + + + + com.example.GaeFirebaseEventProxy.ServletContextListenerImpl + + + index.jsp + + diff --git a/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/webapp/index.jsp b/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/webapp/index.jsp new file mode 100644 index 00000000000..ffc7d85f599 --- /dev/null +++ b/appengine/firebase-event-proxy/gae-firebase-event-proxy/src/main/webapp/index.jsp @@ -0,0 +1,25 @@ +<%-- +Copyright 2015 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. +--%> + +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + + + + + +

Status: up

+ + + diff --git a/appengine/firebase-event-proxy/gae-firebase-listener-python/.gitignore b/appengine/firebase-event-proxy/gae-firebase-listener-python/.gitignore new file mode 100644 index 00000000000..0d20b6487c6 --- /dev/null +++ b/appengine/firebase-event-proxy/gae-firebase-listener-python/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/appengine/firebase-event-proxy/gae-firebase-listener-python/app.yaml b/appengine/firebase-event-proxy/gae-firebase-listener-python/app.yaml new file mode 100644 index 00000000000..f041d384c05 --- /dev/null +++ b/appengine/firebase-event-proxy/gae-firebase-listener-python/app.yaml @@ -0,0 +1,7 @@ +runtime: python27 +api_version: 1 +threadsafe: true + +handlers: +- url: /.* + script: main.app diff --git a/appengine/firebase-event-proxy/gae-firebase-listener-python/main.py b/appengine/firebase-event-proxy/gae-firebase-listener-python/main.py new file mode 100644 index 00000000000..49145bbd749 --- /dev/null +++ b/appengine/firebase-event-proxy/gae-firebase-listener-python/main.py @@ -0,0 +1,38 @@ +# 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 os +import webapp2 + +IS_DEV = os.environ["SERVER_SOFTWARE"][:3] == "Dev" +allowed_users = set() +if IS_DEV: + allowed_users.add("dev-instance") +else: + # Add your Java App Engine proxy App Id here + allowed_users.add("your-java-appengine-proxy-app-id") + +class LoggingHandler(webapp2.RequestHandler): + + def post(self): + user = self.request.headers.get('X-Appengine-Inbound-Appid', None) + if user and user in allowed_users: + firebaseSnapshot = self.request.params['fbSnapshot'] + print firebaseSnapshot + else: + print "Got unauthenticated user: %s" % user + +app = webapp2.WSGIApplication([ + webapp2.Route('/log', LoggingHandler), +])