From dc501313e11632dd42458626d07011ff795c22fa Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Mon, 27 Mar 2017 11:49:19 -0700 Subject: [PATCH 01/39] Initial commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..c4e076f --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# quickstart-python \ No newline at end of file From f02643b0dce7f8f5882fda40a4e77b9ad7d8d62d Mon Sep 17 00:00:00 2001 From: Abe Haskins Date: Fri, 31 Mar 2017 12:13:47 -0700 Subject: [PATCH 02/39] Adds samples --- .gitignore | 3 + CONTRIBUTING.md | 179 ++++++++++++++++++++++++++++++++++++++++ LICENSE | 212 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 10 ++- auth/index.py | 113 ++++++++++++++++++++++++++ 5 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 auth/index.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e356ac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +auth/lib +auth/path +*.pyc diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bd662ff --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,179 @@ +# Contributing to the Firebase Python Quickstarts + +We'd love for you to contribute to our source code and to make the Firebase Python Quickstarts even better than it is today! Here are the guidelines we'd like you to follow: + + - [Code of Conduct](#coc) + - [Question or Problem?](#question) + - [Issues and Bugs](#issue) + - [Feature Requests](#feature) + - [Submission Guidelines](#submit) + - [Coding Rules](#rules) + - [Signing the CLA](#cla) + +## Code of Conduct + +As contributors and maintainers of the Firebase Python Quickstarts project, we pledge to respect everyone who contributes by posting issues, updating documentation, submitting pull requests, providing feedback in comments, and any other activities. + +Communication through any of Firebase's channels (GitHub, StackOverflow, Google+, Twitter, etc.) must be constructive and never resort to personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. + +We promise to extend courtesy and respect to everyone involved in this project regardless of gender, gender identity, sexual orientation, disability, age, race, ethnicity, religion, or level of experience. We expect anyone contributing to the project to do the same. + +If any member of the community violates this code of conduct, the maintainers of the Firebase Python Quickstarts project may take action, removing issues, comments, and PRs or blocking accounts as deemed appropriate. + +If you are subject to or witness unacceptable behavior, or have any other concerns, please drop us a line at nivco@google.com. + +## Got a Question or Problem? + +If you have questions about how to use the Firebase Python Quickstarts, please direct these to [StackOverflow][stackoverflow] and use the `firebase` tag. We are also available on GitHub issues. + +If you feel that we're missing an important bit of documentation, feel free to +file an issue so we can help. Here's an example to get you started: + +``` +What are you trying to do or find out more about? + +Where have you looked? + +Where did you expect to find this information? +``` + +## Found an Issue? +If you find a bug in the source code or a mistake in the documentation, you can help us by +submitting an issue to our [GitHub Repository][github]. Even better you can submit a Pull Request +with a fix. + +See [below](#submit) for some guidelines. + +## Submission Guidelines + +### Submitting an Issue +Before you submit your issue search the archive, maybe your question was already answered. + +If your issue appears to be a bug, and hasn't been reported, open a new issue. +Help us to maximize the effort we can spend fixing issues and adding new +features, by not reporting duplicate issues. Providing the following information will increase the +chances of your issue being dealt with quickly: + +* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps +* **Motivation for or Use Case** - explain why this is a bug for you +* **Browsers and Operating System** - is this a problem with all browsers or only IE9? +* **Reproduce the Error** - provide a live example (using JSBin) or a unambiguous set of steps. +* **Related Issues** - has a similar issue been reported before? +* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be + causing the problem (line of code or commit) + +**If you get help, help others. Good karma rulez!** + +Here's a template to get you started: + +``` +Browser: +Browser version: +Operating system: +Operating system version: + +What steps will reproduce the problem: +1. +2. +3. + +What is the expected result? + +What happens instead of that? + +Please provide any other information below, and attach a screenshot if possible. +``` + +### Submitting a Pull Request +Before you submit your pull request consider the following guidelines: + +* Search [GitHub](https://github.com/firebase/quickstart-nodejs/pulls) for an open or closed Pull Request + that relates to your submission. You don't want to duplicate effort. +* Please sign our [Contributor License Agreement (CLA)](#cla) before sending pull + requests. We cannot accept code without this. +* Make your changes in a new git branch: + + ```shell + git checkout -b my-fix-branch master + ``` + +* Create your patch, **including appropriate test cases**. +* Follow our [Coding Rules](#rules). +* Avoid checking in files that shouldn't be tracked (e.g `node_modules`, `gulp-cache`, `.tmp`, `.idea`). We recommend using a [global](#global-gitignore) gitignore for this. +* Make sure **not** to include a recompiled version of the files found in `/css` and `/js` as part of your PR. We will generate these automatically. +* Commit your changes using a descriptive commit message. + + ```shell + git commit -a + ``` + Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files. + +* Build your changes locally to ensure all the tests pass: + + ```shell + gulp + ``` + +* Push your branch to GitHub: + + ```shell + git push origin my-fix-branch + ``` + +* In GitHub, send a pull request to `/quickstart-nodejs:master`. +* If we suggest changes then: + * Make the required updates. + * Rebase your branch and force push to your GitHub repository (this will update your Pull Request): + + ```shell + git rebase master -i + git push origin my-fix-branch -f + ``` + +That's it! Thank you for your contribution! + +#### After your pull request is merged + +After your pull request is merged, you can safely delete your branch and pull the changes +from the main (upstream) repository: + +* Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows: + + ```shell + git push origin --delete my-fix-branch + ``` + +* Check out the master branch: + + ```shell + git checkout master -f + ``` + +* Delete the local branch: + + ```shell + git branch -D my-fix-branch + ``` + +* Update your master with the latest upstream version: + + ```shell + git pull --ff upstream master + ``` + +## Coding Rules + +We generally follow the [Google JavaScript style guide][java-style-guide]. + +## Signing the CLA + +Please sign our [Contributor License Agreement][google-cla] (CLA) before sending pull requests. For any code +changes to be accepted, the CLA must be signed. It's a quick process, we promise! + +*This guide was inspired by the [AngularJS contribution guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).* + +[github]: https://github.com/firebase/quickstart-java +[google-cla]: https://cla.developers.google.com +[java-style-guide]: https://google.github.io/styleguide/javaguide.html +[stackoverflow]: http://stackoverflow.com/questions/tagged/firebase +[global-gitignore]: https://help.github.com/articles/ignoring-files/#create-a-global-gitignore diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9faf108 --- /dev/null +++ b/LICENSE @@ -0,0 +1,212 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015 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. + + All code in any directories or sub-directories that end with *.html or + *.css is licensed under the Creative Commons Attribution International + 4.0 License, which full text can be found here: + https://creativecommons.org/licenses/by/4.0/legalcode. + + As an exception to this license, all html or css that is generated by + the software at the direction of the user is copyright the user. The + user has full ownership and control over such content, including + whether and how they wish to license it. diff --git a/README.md b/README.md index c4e076f..5eb2b8a 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ -# quickstart-python \ No newline at end of file +# Firebase Quickstarts for Python + +A collection of quickstart samples demonstrating the Firebase APIs using the Admin Python SDK. For more information, see https://firebase.google.com. + +## How to make contributions? +Please read and follow the steps in the [CONTRIBUTING.md](CONTRIBUTING.md) + +## License +See [LICENSE](LICENSE) diff --git a/auth/index.py b/auth/index.py new file mode 100644 index 0000000..93e092a --- /dev/null +++ b/auth/index.py @@ -0,0 +1,113 @@ +import sys +sys.path.append("lib") + +# [START import_sdk] +import firebase_admin +# [END import_sdk] +from firebase_admin import credentials +from firebase_admin import auth + +def initialize_sdk_with_service_account(): + # [START initialize_sdk_with_service_account] + import firebase_admin + from firebase_admin import credentials + + cred = credentials.Certificate('path/to/service.json') + default_app = firebase_admin.initialize_app(cred) + # [END initialize_sdk_with_service_account] + firebase_admin.delete_app(default_app) + +def initialize_sdk_with_application_default(): + # [START initialize_sdk_with_application_default] + default_app = firebase_admin.initialize_app() + # [END initialize_sdk_with_application_default] + firebase_admin.delete_app(default_app) + +def initialize_sdk_with_refresh_token(): + # [START initialize_sdk_with_refresh_token] + cred = credentials.RefreshToken('path/to/refreshToken.json') + default_app = firebase_admin.initialize_app(cred) + # [END initialize_sdk_with_refresh_token] + firebase_admin.delete_app(default_app) + +def access_services_default(): + cred = credentials.Certificate('path/to/service.json') + # [START access_services_default] + # Import the Firebase service + from firebase_admin import auth + + # Initialize the default app + default_app = firebase_admin.initialize_app(cred) + print(default_app.name); # "[DEFAULT]" + + # Retrieve services via the auth package... + # auth.create_custom_token(...) + # [END access_services_default] + firebase_admin.delete_app(default_app) + +def access_services_nondefault(): + cred = credentials.Certificate('path/to/service.json') + otherCred = credentials.Certificate('path/to/service.json') + + # [START access_services_nondefault] + # Initialize the default app + default_app = firebase_admin.initialize_app(cred) + + # Initialize another app with a different config + other_app = firebase_admin.initialize_app(cred, name='other') + + print(default_app.name); # "[DEFAULT]" + print(other_app.name); # "other" + + # Retrieve default services via the auth package... + # auth.create_custom_token(...) + + # Use the `app` argument to retrieve the other app's services + # auth.create_custom_token(..., app=other_app) + # [END access_services_nondefault] + firebase_admin.delete_app(default_app) + firebase_admin.delete_app(other_app) + +def create_token_uid(): + cred = credentials.Certificate('path/to/service.json') + default_app = firebase_admin.initialize_app(cred) + # [START create_token_uid] + uid = 'some-uid' + + customToken = auth.create_custom_token(uid) + # [END create_token_uid] + firebase_admin.delete_app(default_app) + return customToken + +def create_token_with_claims(): + cred = credentials.Certificate('path/to/service.json') + default_app = firebase_admin.initialize_app(cred) + # [START create_token_with_claims] + uid = 'some-uid' + additional_claims = { + "premiumAccount": True + } + + custom_token = auth.create_custom_token(uid, additional_claims) + # [END create_token_with_claims] + firebase_admin.delete_app(default_app) + return custom_token + +def verify_token_uid(id_token): + cred = credentials.Certificate('path/to/service.json') + default_app = firebase_admin.initialize_app(cred) + # [START verify_token_uid] + customToken = auth.verify_id_token(id_token) + uid = customToken.uid + # [END verify_token_uid] + print(uid) + firebase_admin.delete_app(default_app) + +initialize_sdk_with_service_account() +initialize_sdk_with_application_default() +#initialize_sdk_with_refresh_token() +access_services_default() +access_services_nondefault() +create_token_uid() +token_with_claims = create_token_with_claims() +#verify_token_uid() From 2c4dbf76bbde0a6cee4ce1d430d4191772260099 Mon Sep 17 00:00:00 2001 From: Abe Haskins Date: Fri, 31 Mar 2017 12:18:28 -0700 Subject: [PATCH 03/39] customToken -> decodedToken --- auth/index.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth/index.py b/auth/index.py index 93e092a..ad2fc03 100644 --- a/auth/index.py +++ b/auth/index.py @@ -97,8 +97,8 @@ def verify_token_uid(id_token): cred = credentials.Certificate('path/to/service.json') default_app = firebase_admin.initialize_app(cred) # [START verify_token_uid] - customToken = auth.verify_id_token(id_token) - uid = customToken.uid + decoded_token = auth.verify_id_token(id_token) + uid = decoded_token.uid # [END verify_token_uid] print(uid) firebase_admin.delete_app(default_app) From 075590a5c5f5e5fa3bc97a27c824b3052b857650 Mon Sep 17 00:00:00 2001 From: Abe Haskins Date: Fri, 31 Mar 2017 14:10:23 -0700 Subject: [PATCH 04/39] Fixes quotes --- auth/index.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth/index.py b/auth/index.py index ad2fc03..0ab70ee 100644 --- a/auth/index.py +++ b/auth/index.py @@ -85,7 +85,7 @@ def create_token_with_claims(): # [START create_token_with_claims] uid = 'some-uid' additional_claims = { - "premiumAccount": True + 'premiumAccount': True } custom_token = auth.create_custom_token(uid, additional_claims) @@ -98,7 +98,7 @@ def verify_token_uid(id_token): default_app = firebase_admin.initialize_app(cred) # [START verify_token_uid] decoded_token = auth.verify_id_token(id_token) - uid = decoded_token.uid + uid = decoded_token['uid'] # [END verify_token_uid] print(uid) firebase_admin.delete_app(default_app) From 87661e93f2f8b8ce1cb67fc6fa240ead19030b27 Mon Sep 17 00:00:00 2001 From: Abe Haskins Date: Fri, 31 Mar 2017 14:50:02 -0700 Subject: [PATCH 05/39] Naming / comment tweaks --- README.md | 2 +- auth/index.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5eb2b8a..77742e3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Firebase Quickstarts for Python -A collection of quickstart samples demonstrating the Firebase APIs using the Admin Python SDK. For more information, see https://firebase.google.com. +A collection of quickstart samples demonstrating the Firebase APIs using the Admin Python SDK. For more information, see https://firebase.google.com/docs/admin/setup. ## How to make contributions? Please read and follow the steps in the [CONTRIBUTING.md](CONTRIBUTING.md) diff --git a/auth/index.py b/auth/index.py index 0ab70ee..717d282 100644 --- a/auth/index.py +++ b/auth/index.py @@ -12,7 +12,7 @@ def initialize_sdk_with_service_account(): import firebase_admin from firebase_admin import credentials - cred = credentials.Certificate('path/to/service.json') + cred = credentials.Certificate('path/to/serviceAccountKey.json') default_app = firebase_admin.initialize_app(cred) # [END initialize_sdk_with_service_account] firebase_admin.delete_app(default_app) @@ -97,6 +97,8 @@ def verify_token_uid(id_token): cred = credentials.Certificate('path/to/service.json') default_app = firebase_admin.initialize_app(cred) # [START verify_token_uid] + # id_token comes from the client app (shown above) + decoded_token = auth.verify_id_token(id_token) uid = decoded_token['uid'] # [END verify_token_uid] From 8ed0c94c2aa1b433d90d0068aa49d7af18d65188 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Tue, 4 Apr 2017 13:09:57 -0700 Subject: [PATCH 06/39] Fixing variable name for consistency --- auth/index.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth/index.py b/auth/index.py index 717d282..87c3d55 100644 --- a/auth/index.py +++ b/auth/index.py @@ -74,10 +74,10 @@ def create_token_uid(): # [START create_token_uid] uid = 'some-uid' - customToken = auth.create_custom_token(uid) + custom_token = auth.create_custom_token(uid) # [END create_token_uid] firebase_admin.delete_app(default_app) - return customToken + return custom_token def create_token_with_claims(): cred = credentials.Certificate('path/to/service.json') From 03b2ae927f3f3d688610b7871fdc1788dd321a59 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Wed, 21 Jun 2017 11:37:40 -0700 Subject: [PATCH 07/39] Adding database samples --- db/index.py | 208 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 db/index.py diff --git a/db/index.py b/db/index.py new file mode 100644 index 0000000..0f53091 --- /dev/null +++ b/db/index.py @@ -0,0 +1,208 @@ +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db + +def authenticate_with_admin_privileges(): + # [START authenticate_with_admin_privileges] + import firebase_admin + from firebase_admin import credentials + from firebase_admin import db + + # Fetch the service account key JSON file contents + cred = credentials.Certificate('path/to/serviceAccountKey.json') + + # Initialize the app with a service account, granting admin privileges + firebase_admin.initialize_app(cred, { + 'databaseURL': 'https://databaseName.firebaseio.com' + }) + + # As an admin, the app has access to read and write all data, regradless of Security Rules + ref = db.reference('restricted_access/secret_document') + print(ref.get()) + # [END authenticate_with_admin_privileges] + firebase_admin.delete_app(firebase_admin.get_app()) + +def authenticate_with_limited_privileges(): + # [START authenticate_with_limited_privileges] + import firebase_admin + from firebase_admin import credentials + from firebase_admin import db + + # Fetch the service account key JSON file contents + cred = credentials.Certificate('path/to/serviceAccountKey.json') + + # Initialize the app with a custom auth variable, limiting the server's access + firebase_admin.initialize_app(cred, { + 'databaseURL': 'https://databaseName.firebaseio.com', + 'databaseAuthVariableOverride': { + 'uid': 'my-service-worker' + } + }) + + # The app only has access as defined in the Security Rules + ref = db.reference('/some_resource') + print(ref.get()) + # [END authenticate_with_limited_privileges] + firebase_admin.delete_app(firebase_admin.get_app()) + +def authenticate_with_guest_privileges(): + # [START authenticate_with_guest_privileges] + import firebase_admin + from firebase_admin import credentials + from firebase_admin import db + + # Fetch the service account key JSON file contents + cred = credentials.Certificate('path/to/serviceAccountKey.json') + + # Initialize the app with a None auth variable, limiting the server's access + firebase_admin.initialize_app(cred, { + 'databaseURL': 'https://databaseName.firebaseio.com', + 'databaseAuthVariableOverride': None + }) + + # The app only has access to public data as defined in the Security Rules + ref = db.reference('/some_resource') + print(ref.get()) + # [END authenticate_with_guest_privileges] + firebase_admin.delete_app(firebase_admin.get_app()) + +def get_reference(): + # [START get_reference] + # Get a database reference to our blog. + ref = db.reference('server/saving-data/fireblog') + # [END get_reference] + print(ref.key) + +def set_value(): + ref = db.reference('server/saving-data/fireblog') + + # [START set_value] + users_ref = ref.child('users') + users_ref.set({ + 'alanisawesome': { + 'date_of_birth': 'June 23, 1912', + 'full_name': 'Alan Turing' + }, + 'gracehop': { + 'date_of_birth': 'December 9, 1906', + 'full_name': 'Grace Hopper' + } + }) + # [END set_value] + +def set_child_value(): + ref = db.reference('server/saving-data/fireblog') + users_ref = ref.child('users') + + # [START set_child_value] + users_ref.child('alanisawesome').set({ + 'date_of_birth': 'June 23, 1912', + 'full_name': 'Alan Turing' + }) + users_ref.child('gracehop').set({ + 'date_of_birth': 'December 9, 1906', + 'full_name': 'Grace Hopper' + }) + # [END set_child_value] + +def update_child(): + ref = db.reference('server/saving-data/fireblog') + users_ref = ref.child('users') + + # [START update_child] + hopper_ref = users_ref.child('gracehop') + hopper_ref.update({ + 'nickname': 'Amazing Grace' + }) + # [END update_child] + +def update_children(): + ref = db.reference('server/saving-data/fireblog') + users_ref = ref.child('users') + + # [START update_children] + users_ref.update({ + 'alanisawesome/nickname': 'Alan The Machine', + 'gracehop/nickname': 'Amazing Grace' + }) + # [END update_children] + +def overwrite_value(): + ref = db.reference('server/saving-data/fireblog') + users_ref = ref.child('users') + + # [START overwrite_value] + users_ref.update({ + 'alanisawesome': { + 'nickname': 'Alan The Machine' + }, + 'gracehop': { + 'nickname': 'Amazing Grace' + } + }) + # [END overwrite_value] + +def push_value(): + ref = db.reference('server/saving-data/fireblog') + + # [START push_value] + posts_ref = ref.child('posts') + + new_post_ref = posts_ref.push() + new_post_ref.set({ + 'author': 'gracehop', + 'title': 'Announcing COBOL, a New Programming Language' + }) + + # We can also chain the two calls together + posts_ref.push().set({ + 'author': 'alanisawesome', + 'title': 'The Turing Machine' + }) + # [END push_value] + +def push_and_set_value(): + ref = db.reference('server/saving-data/fireblog') + posts_ref = ref.child('posts') + + # [START push_and_set_value] + # This is equivalent to the calls to push().set(...) above + posts_ref.push({ + 'author': 'gracehop', + 'title': 'Announcing COBOL, a New Programming Language' + }) + # [END push_and_set_value] + +def get_push_key(): + ref = db.reference('server/saving-data/fireblog') + posts_ref = ref.child('posts') + + # [START push_key] + # Generate a reference to a new location and add some data using push() + new_post_ref = posts_ref.push() + + # Get the unique key generated by push() + post_id = new_post_ref.key + # [END push_key] + print(post_id) + + +service_account = '/usr/local/google/home/hkj/Projects/firebase-admin-python/public/scripts/cert.json' +database_url = 'https://admin-java-integration.firebaseio.com' + +cred = credentials.Certificate(service_account) +firebase_admin.initialize_app(cred, { + 'databaseURL': database_url +}) + +get_reference() +set_value() +set_child_value() +update_child() +update_children() +overwrite_value() +push_value() +push_and_set_value() +get_push_key() + +firebase_admin.delete_app(firebase_admin.get_app()) \ No newline at end of file From 0e4a4cfbabf08c8e98b9ead37e17a52861860389 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Wed, 21 Jun 2017 13:01:09 -0700 Subject: [PATCH 08/39] Added samples for read operations and complex queries --- db/index.py | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 2 deletions(-) diff --git a/db/index.py b/db/index.py index 0f53091..ce1222c 100644 --- a/db/index.py +++ b/db/index.py @@ -186,9 +186,116 @@ def get_push_key(): # [END push_key] print(post_id) +def read_value(): + # [START read_value] + # Get a database reference to our posts + ref = db.reference('server/saving-data/fireblog/posts') -service_account = '/usr/local/google/home/hkj/Projects/firebase-admin-python/public/scripts/cert.json' -database_url = 'https://admin-java-integration.firebaseio.com' + # Read the data at the posts reference (this is a blocking operation) + print(ref.get()) + # [END read_value] + +def order_by_child(): + # [START order_by_child] + ref = db.reference('dinosaurs') + snapshot = ref.order_by_child('height').get() + for key, val in snapshot.items(): + print('{0} was {1} meters tall'.format(key, val)) + # [END order_by_child] + +def order_by_nested_child(): + # [START order_by_nested_child] + ref = db.reference('dinosaurs') + snapshot = ref.order_by_child('dimensions/height').get() + for key, val in snapshot.items(): + print('{0} was {1} meters tall'.format(key, val)) + # [END order_by_nested_child] + +def order_by_key(): + # [START order_by_key] + ref = db.reference('dinosaurs') + snapshot = ref.order_by_key().get() + print(snapshot) + # [END order_by_key] + +def order_by_value(): + # [START order_by_value] + ref = db.reference('scores') + snapshot = ref.order_by_value().get() + for key, val in snapshot.items(): + print('The {0} dinosaur\'s score is {1}'.format(key, val)) + # [END order_by_value] + +def limit_query(): + # [START limit_query_1] + ref = db.reference('dinosaurs') + snapshot = ref.order_by_child('weight').limit_to_last(2).get() + for key in snapshot: + print(key) + # [END limit_query_1] + + # [START limit_query_2] + ref = db.reference('dinosaurs') + snapshot = ref.order_by_child('height').limit_to_first(2).get() + for key in snapshot: + print(key) + # [END limit_query_2] + + # [START limit_query_3] + scores_ref = db.reference('scores') + snapshot = scores_ref.order_by_value().limit_to_last(3).get() + for key, val in snapshot.items(): + print('The {0} dinosaur\'s score is {1}'.format(key, val)) + # [END limit_query_3] + +def range_query(): + # [START range_query_1] + ref = db.reference('dinosaurs') + snapshot = ref.order_by_child('height').start_at(3).get() + for key in snapshot: + print(key) + # [END range_query_1] + + # [START range_query_2] + ref = db.reference('dinosaurs') + snapshot = ref.order_by_key().end_at('pterodactyl').get() + for key in snapshot: + print(key) + # [END range_query_2] + + # [START range_query_3] + ref = db.reference('dinosaurs') + snapshot = ref.order_by_key().start_at('b').end_at(u'b\uf8ff').get() + for key in snapshot: + print(key) + # [END range_query_3] + + # [START range_query_4] + ref = db.reference('dinosaurs') + snapshot = ref.order_by_child('height').equal_to(25).get() + for key in snapshot: + print(key) + # [END range_query_4] + +def complex_query(): + # [START complex_query] + ref = db.reference('dinosaurs') + favotire_dino_height = ref.child('stegosaurus').child('height').get() + query = ref.order_by_child('height').end_at(favotire_dino_height).limit_to_last(2) + snapshot = query.get() + if len(snapshot) == 2: + # Data is ordered by increasing height, so we want the first entry. + # Second entry is stegosarus. + for key in snapshot: + print('The dinosaur just shorter than the stegosaurus is {0}'.format(key)) + return + else: + print('The stegosaurus is the shortest dino') + # [END complex_query] + + +service_account = 'path/to/serviceAccount.json' +database_url = 'https://databaseName.firebaseio.com' cred = credentials.Certificate(service_account) firebase_admin.initialize_app(cred, { @@ -205,4 +312,13 @@ def get_push_key(): push_and_set_value() get_push_key() +read_value() +order_by_child() +#order_by_nested_child() +order_by_key() +order_by_value() +limit_query() +range_query() +complex_query() + firebase_admin.delete_app(firebase_admin.get_app()) \ No newline at end of file From 1df3b22503203e1ff6c8752e6f0acac6eea187e4 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Wed, 21 Jun 2017 13:10:19 -0700 Subject: [PATCH 09/39] New line at end of file --- db/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/index.py b/db/index.py index ce1222c..1bc748c 100644 --- a/db/index.py +++ b/db/index.py @@ -321,4 +321,4 @@ def complex_query(): range_query() complex_query() -firebase_admin.delete_app(firebase_admin.get_app()) \ No newline at end of file +firebase_admin.delete_app(firebase_admin.get_app()) From df4ef2858f1433d2a092b7622528841c27cfe06f Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 22 Jun 2017 17:08:40 -0700 Subject: [PATCH 10/39] Adding the import statement to the 2 setup examples. --- db/index.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/db/index.py b/db/index.py index 1bc748c..78f7d9e 100644 --- a/db/index.py +++ b/db/index.py @@ -68,6 +68,9 @@ def authenticate_with_guest_privileges(): def get_reference(): # [START get_reference] + # Import database module. + from firebase_admin import db + # Get a database reference to our blog. ref = db.reference('server/saving-data/fireblog') # [END get_reference] @@ -188,6 +191,9 @@ def get_push_key(): def read_value(): # [START read_value] + # Import database module. + from firebase_admin import db + # Get a database reference to our posts ref = db.reference('server/saving-data/fireblog/posts') From 220c3dd146a407d68da447a980ec0bae90ed0d04 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Fri, 23 Jun 2017 13:18:05 -0700 Subject: [PATCH 11/39] Updating resource name for guest access example --- db/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/index.py b/db/index.py index 78f7d9e..b8711d6 100644 --- a/db/index.py +++ b/db/index.py @@ -61,7 +61,7 @@ def authenticate_with_guest_privileges(): }) # The app only has access to public data as defined in the Security Rules - ref = db.reference('/some_resource') + ref = db.reference('/public_resource') print(ref.get()) # [END authenticate_with_guest_privileges] firebase_admin.delete_app(firebase_admin.get_app()) From ffb07704c26087b6441b8df3623ccb69a7f3bc6b Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Wed, 2 Aug 2017 11:00:05 -0700 Subject: [PATCH 12/39] Added user management samples --- auth/index.py | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/auth/index.py b/auth/index.py index 87c3d55..44b03ff 100644 --- a/auth/index.py +++ b/auth/index.py @@ -105,6 +105,74 @@ def verify_token_uid(id_token): print(uid) firebase_admin.delete_app(default_app) +def get_user(uid): + # [START get_user] + from firebase_admin import auth + + user = auth.get_user(uid) + print 'Successfully fetched user data: {0}'.format(user.uid) + # [END get_user] + +def get_user_by_email(): + email = 'user@example.com' + # [START get_user_by_email] + from firebase_admin import auth + + user = auth.get_user_by_email(email) + print 'Successfully fetched user data: {0}'.format(user.uid) + # [END get_user_by_email] + +def get_user_by_phone_number(): + phone = '+1 123 456 7890' + # [START get_user_by_phone] + from firebase_admin import auth + + user = auth.get_user_by_phone_number(phone) + print 'Successfully fetched user data: {0}'.format(user.uid) + # [END get_user_by_phone] + +def create_user(): + # [START create_user] + user = auth.create_user( + email='user@example.com', + email_verified=False, + phone_number='+11234567890', + password='secretPassword', + display_name='John Doe', + photo_url='http://www.example.com/12345678/photo.png', + disabled=False) + print 'Sucessfully created new user: {0}'.format(user.uid) + # [END create_user] + return user.uid + +def create_user_with_id(): + # [START create_user_with_id] + user = auth.create_user( + uid='some-uid', email='user@example.com', phone_number='+11234567890') + print 'Sucessfully created new user: {0}'.format(user.uid) + # [END create_user_with_id] + +def update_user(uid): + # [START update_user] + user = auth.update_user( + uid, + email='user@example.com', + phone_number='+11234567890', + email_verified=True, + password='newPassword', + display_name='John Doe', + photo_url='http://www.example.com/12345678/photo.png', + disabled=True) + print 'Sucessfully updated user: {0}'.format(user.uid) + # [END update_user] + +def delete_user(uid): + # [START delete_user] + auth.delete_user(uid) + print 'Successfully deleted user' + # [END delete_user] + + initialize_sdk_with_service_account() initialize_sdk_with_application_default() #initialize_sdk_with_refresh_token() @@ -113,3 +181,11 @@ def verify_token_uid(id_token): create_token_uid() token_with_claims = create_token_with_claims() #verify_token_uid() + +uid = create_user() +create_user_with_id() +get_user(uid) +get_user_by_email() +get_user_by_phone_number() +update_user(uid) +delete_user(uid) From dcf0cfc8c2b37aa8f977cbccaeec0fb4a30a0e6d Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Wed, 9 Aug 2017 11:19:56 -0700 Subject: [PATCH 13/39] Added a sample for RTDB transaction support --- db/index.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/db/index.py b/db/index.py index b8711d6..a06e82b 100644 --- a/db/index.py +++ b/db/index.py @@ -189,6 +189,15 @@ def get_push_key(): # [END push_key] print(post_id) +def run_transaction(): + # [START transaction] + def increment_votes(current_value): + return current_value + 1 if current_value else 1 + + upvotes_ref = db.reference('server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes') + upvotes_ref.transaction(increment_votes) + # [END transaction] + def read_value(): # [START read_value] # Import database module. @@ -317,6 +326,7 @@ def complex_query(): push_value() push_and_set_value() get_push_key() +run_transaction() read_value() order_by_child() From 90f09ef48d8e70b951f38676b051391461ccb431 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Fri, 11 Aug 2017 10:49:28 -0700 Subject: [PATCH 14/39] Updated phone numbers to use the 555 prefix --- auth/index.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/auth/index.py b/auth/index.py index 44b03ff..d4b5397 100644 --- a/auth/index.py +++ b/auth/index.py @@ -123,7 +123,7 @@ def get_user_by_email(): # [END get_user_by_email] def get_user_by_phone_number(): - phone = '+1 123 456 7890' + phone = '+1 555 555 0100' # [START get_user_by_phone] from firebase_admin import auth @@ -136,7 +136,7 @@ def create_user(): user = auth.create_user( email='user@example.com', email_verified=False, - phone_number='+11234567890', + phone_number='+15555550100', password='secretPassword', display_name='John Doe', photo_url='http://www.example.com/12345678/photo.png', @@ -148,7 +148,7 @@ def create_user(): def create_user_with_id(): # [START create_user_with_id] user = auth.create_user( - uid='some-uid', email='user@example.com', phone_number='+11234567890') + uid='some-uid', email='user@example.com', phone_number='+15555550100') print 'Sucessfully created new user: {0}'.format(user.uid) # [END create_user_with_id] @@ -157,7 +157,7 @@ def update_user(uid): user = auth.update_user( uid, email='user@example.com', - phone_number='+11234567890', + phone_number='+15555550100', email_verified=True, password='newPassword', display_name='John Doe', From b0792a73106b9c5d2783fffef67969f5bb63552d Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Fri, 11 Aug 2017 17:29:28 -0700 Subject: [PATCH 15/39] Showing how to handle txn error --- db/index.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/db/index.py b/db/index.py index a06e82b..d58f0f7 100644 --- a/db/index.py +++ b/db/index.py @@ -195,7 +195,11 @@ def increment_votes(current_value): return current_value + 1 if current_value else 1 upvotes_ref = db.reference('server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes') - upvotes_ref.transaction(increment_votes) + try: + new_vote_count = upvotes_ref.transaction(increment_votes) + print 'Transaction completed' + except db.TransactionError: + print 'Transaction failed to commit' # [END transaction] def read_value(): From 026cadc773f09b407db8f85136bed760b765c8bd Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Wed, 2 Aug 2017 09:38:55 -0700 Subject: [PATCH 16/39] Add python sample Change-Id: I8fc382152810d0df57e0468e53d8c25744a17e47 --- messaging/.gitignore | 1 + messaging/README.md | 100 +++++++++++++++++++++++++++++++++ messaging/messaging.py | 124 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+) create mode 100644 messaging/.gitignore create mode 100644 messaging/README.md create mode 100644 messaging/messaging.py diff --git a/messaging/.gitignore b/messaging/.gitignore new file mode 100644 index 0000000..fe6109d --- /dev/null +++ b/messaging/.gitignore @@ -0,0 +1 @@ +service-account.json \ No newline at end of file diff --git a/messaging/README.md b/messaging/README.md new file mode 100644 index 0000000..eaa58a8 --- /dev/null +++ b/messaging/README.md @@ -0,0 +1,100 @@ +Firebase Cloud Messaging Python Quickstart +========================================== + +The Firebase Cloud Messaging Python quickstart app demonstrates sending +notification messages to a topic. All clients subscribed to the topic +will receive the message. + +Introduction +------------ + +This is a simple example of using Firebase Cloud Messaging REST API to send +the same message to different platforms. To learn more about how you can use +Firebase Cloud Messaging REST API in your app, see [About Cloud Messaging Server](https://firebase.google.com/docs/cloud-messaging/server/). + +Getting started +--------------- + +1. [Add Firebase to your Android Project](https://firebase.google.com/docs/android/setup). +2. Create a service account as described in [Adding Firebase to your Server](https://firebase.google.com/docs/admin/setup) and download the JSON file. + - Copy the json file to this folder and rename it to `service-account.json`. +3. Change the `PROJECT_ID` variable in `messaging.py` to your project ID. + +Run +--- +- This sample allows you to send two types of messages: + + 1. A message that uses the common `notification` object of the API. It is used to define + the `title` and `body` of a notification message (display message). To send this message, from the + `messaging` directory run: + + python messaging.py --message=common-message + + 2. A message that uses the common `notification` object of the API as well as tha `android` and + `apns` objects to customize the messages received on the corresponding platforms. To send this + message, from the `messaging` directory run: + + python messaging.py --message=override-message + +- Any client devices that you have subscribed to the news topic should receive + a notification message. + + 1. When you use the `common-message` option, clients receive a simple notification message + with only title and body defined. + + 2. When you use the `override-message` option, clients receive a simple notification message + with title and body defined. In addition: + - iOS devices would receive high priority messages and if the app is in the background then a + badge will be applied to the app's icon. + + - Android devices would receive a message with a `click_action`. In this case it is set + to the default intent, but this could be customized to any intent available in your app. + +Best practices +-------------- +This section provides some additional information about how the FCM payloads can +be used to target different platforms. + +### Common payloads ### + +In many cases you may want to send the same message to multiple platforms. If +this is a notification message (display notification) then you can use the +common payloads. These are payloads that are automatically translated to their +platform equivalent payloads. + +### Platform customizations ### + +In cases where you would like to customize the message for the different platforms +that will receive the message, use the platform specific objects to add or override +anything set by the common fields. + +For example, if you want to send a notification message (display notification) to all platforms +but you would like to include accompanying data to Android clients receiving the +message, then you can use the `android` object to define a `data` payload that will +be appended to the notification message when sent to Android clients. + +Support +------- + +- [Stack Overflow](https://stackoverflow.com/questions/tagged/firebase-cloud-messaging) +- [Firebase Support](https://firebase.google.com/support/) + +License +------- + +Copyright 2016 Google, Inc. + +Licensed to the Apache Software Foundation (ASF) under one or more contributor +license agreements. See the NOTICE file distributed with this work for +additional information regarding copyright ownership. The ASF licenses this +file to you 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. diff --git a/messaging/messaging.py b/messaging/messaging.py new file mode 100644 index 0000000..b68cbe8 --- /dev/null +++ b/messaging/messaging.py @@ -0,0 +1,124 @@ +"""Server Side FCM sample. + +Firebase Cloud Messaging (FCM) can be used to send messages to clients on iOS, +Android and Web. + +This sample uses FCM to send two types of messages to clients that are subscribed +to the `news` topic. One type of message is a simple notification message (display message). +The other is a notification message (display notification) with platform specific +customizations. For example, a badge is added to messages that are sent to iOS devices. +""" + +import argparse +import json +import requests + +from oauth2client.service_account import ServiceAccountCredentials + +PROJECT_ID = 'gh-quickstarter-278bf' +BASE_URL = 'https://fcm-staging.sandbox.googleapis.com' +FCM_ENDPOINT = 'v1/projects/' + PROJECT_ID + '/messages:send' +FCM_URL = BASE_URL + '/' + FCM_ENDPOINT +FCM_SCOPE = 'https://www.googleapis.com/auth/firebase.messaging' + +# [START retrieve_access_token] +def _get_access_token(): + """Retrieve a valid access token that can be used to authorize requests. + + :return: Access token. + """ + credentials = ServiceAccountCredentials.from_json_keyfile_name( + 'service-account.json', FCM_SCOPE) + access_token_info = credentials.get_access_token() + return access_token_info.access_token +# [END retrieve_access_token] + +def _send_fcm_message(fcm_message): + """Send HTTP request to FCM with given message. + + Args: + fcm_message: JSON object that will make up the body of the request. + """ + # [START use_access_token] + headers = { + 'Authorization': 'Bearer ' + _get_access_token(), + 'Content-Type': 'application/json; UTF-8', + } + # [END use_access_token] + resp = requests.post(FCM_URL, data=json.dumps(fcm_message), headers=headers) + + if resp.status_code == 200: + print('Message sent to Firebase for delivery, response:') + print(resp.text) + else: + print('Unable to send message to Firebase') + print(resp.text) + +def _build_common_message(): + """Construct common notifiation message. + + Construct a JSON object that will be used to define the + common parts of a notification message that will be sent + to any app instance subscribed to the news topic. + """ + return { + 'message': { + 'topic': 'news', + 'notification': { + 'title': 'FCM Notification', + 'body': 'Notification from FCM' + } + } + } + +def _build_override_message(): + """Construct common notification message with overrides. + + Constructs a JSON object that will be used to customize + the messages that are sent to iOS and Android devices. + """ + fcm_message = _build_common_message() + + apns_override = { + 'payload': { + 'aps': { + 'badge': 1 + } + }, + 'headers': { + 'apns-priority': '10' + } + } + + android_override = { + 'notification': { + 'click_action': 'android.intent.action.MAIN' + } + } + + fcm_message['message']['android'] = android_override + fcm_message['message']['apns'] = apns_override + + return fcm_message + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--message') + args = parser.parse_args() + if args.message and args.message == 'common-message': + common_message = _build_common_message() + print('FCM request body for message using common notification object:') + print(json.dumps(common_message, indent=2)) + _send_fcm_message(common_message) + elif args.message and args.message == 'override-message': + override_message = _build_override_message() + print('FCM request body for override message:') + print(json.dumps(override_message, indent=2)) + _send_fcm_message(override_message) + else: + print('''Invalid command. Please use one of the following commands: +python messaging.py --message=common-message +python messaging.py --message=override-message''') + +if __name__ == '__main__': + main() From 9e9ebaeb47dde77550293b9493ad2c1613f186c9 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Fri, 27 Oct 2017 11:22:25 -0700 Subject: [PATCH 17/39] Use proper base url Change-Id: I9799e7945e8d2936df9bce6f243e0e43acce22b1 --- messaging/messaging.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messaging/messaging.py b/messaging/messaging.py index b68cbe8..a5ef396 100644 --- a/messaging/messaging.py +++ b/messaging/messaging.py @@ -15,8 +15,8 @@ from oauth2client.service_account import ServiceAccountCredentials -PROJECT_ID = 'gh-quickstarter-278bf' -BASE_URL = 'https://fcm-staging.sandbox.googleapis.com' +PROJECT_ID = '' +BASE_URL = 'https://fcm.googleapis.com' FCM_ENDPOINT = 'v1/projects/' + PROJECT_ID + '/messages:send' FCM_URL = BASE_URL + '/' + FCM_ENDPOINT FCM_SCOPE = 'https://www.googleapis.com/auth/firebase.messaging' From 609a913de498323da62afa1ed7bbf8f7110accb2 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Fri, 27 Oct 2017 15:19:24 -0700 Subject: [PATCH 18/39] Add dependencies to README Change-Id: I1e32e8402c0c1c1e4066307fff0f804e5d6cea08 --- messaging/README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/messaging/README.md b/messaging/README.md index eaa58a8..c6c705e 100644 --- a/messaging/README.md +++ b/messaging/README.md @@ -15,10 +15,13 @@ Firebase Cloud Messaging REST API in your app, see [About Cloud Messaging Server Getting started --------------- -1. [Add Firebase to your Android Project](https://firebase.google.com/docs/android/setup). -2. Create a service account as described in [Adding Firebase to your Server](https://firebase.google.com/docs/admin/setup) and download the JSON file. +1. Create a service account as described in [Adding Firebase to your Server](https://firebase.google.com/docs/admin/setup) and download the JSON file. - Copy the json file to this folder and rename it to `service-account.json`. -3. Change the `PROJECT_ID` variable in `messaging.py` to your project ID. +2. Change the `PROJECT_ID` variable in `messaging.py` to your project ID. +3. Ensure you install `requests` and `google-api-python-client` + + easy_install --upgrade requests + easy_install --upgrade google-api-python-client Run --- From 11cc921e335b70ff5c827d43c2ef9eeb7a0ac35e Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Mon, 27 Nov 2017 16:54:33 -0800 Subject: [PATCH 19/39] Adding Python samples for list_users() and set_custom_user_claims() --- auth/index.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/auth/index.py b/auth/index.py index d4b5397..42cd783 100644 --- a/auth/index.py +++ b/auth/index.py @@ -172,6 +172,63 @@ def delete_user(uid): print 'Successfully deleted user' # [END delete_user] +def set_custom_user_claims(uid): + # [START set_custom_user_claims] + auth.set_custom_user_claims(uid, {'admin': True}) + # [END set_custom_user_claims] + + id_token = 'id_token' + # [START verify_custom_claims] + claims = auth.verify_id_token(id_token) + if claims['admin'] is True: + # Allow access to requested admin resource. + pass + # [END verify_custom_claims] + + # [START read_custom_user_claims] + user = auth.get_user(uid) + print user.custom_claims + # [END read_custom_user_claims] + +def set_custom_user_claims_script(): + # [START set_custom_user_claims_script] + user = auth.get_user_by_email('user@admin.example.com') + # Confirm user is verified + if user.email_verified: + # Add custom claims for additional privileges. + # This will be picked up by the user on token refresh or next sign in on new device. + auth.set_custom_user_claims(user.uid, { + 'admin': True + }) + # [END set_custom_user_claims_script] + +def set_custom_user_claims_incremental(): + # [START set_custom_user_claims_incremental] + user = auth.get_user_by_email('user@admin.example.com') + # Add incremental custom claim without overwriting existing claims. + current_custom_claims = user.custom_claims + if current_custom_claims.get('admin'): + # Add level. + current_custom_claims['accessLevel'] = 10 + # Add custom claims for additional privileges. + auth.set_custom_user_claims(user.uid, current_custom_claims) + # [END set_custom_user_claims_incremental] + +def list_all_users(): + # [START list_all_users] + # Start listing users from the beginning, 1000 at a time. + page = auth.list_users() + while page: + for user in page.users: + print 'User: ' + user.uid + # Get next batch of users. + page = page.get_next_page() + + # Iterate through all users. This will still retrieve users in batches, + # buffering no more than 1000 users in memory at a time. + for user in auth.list_users().iterate_all(): + print 'User: ' + user.uid + # [END list_all_users] initialize_sdk_with_service_account() initialize_sdk_with_application_default() @@ -188,4 +245,6 @@ def delete_user(uid): get_user_by_email() get_user_by_phone_number() update_user(uid) +set_custom_user_claims(uid) +list_all_users() delete_user(uid) From de680129217716efc17796b092457eea5ce9ed3b Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Mon, 27 Nov 2017 16:58:31 -0800 Subject: [PATCH 20/39] Added more comments --- auth/index.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/auth/index.py b/auth/index.py index 42cd783..6e5ff10 100644 --- a/auth/index.py +++ b/auth/index.py @@ -174,11 +174,15 @@ def delete_user(uid): def set_custom_user_claims(uid): # [START set_custom_user_claims] + # Set admin privilege on the user corresponding to uid. auth.set_custom_user_claims(uid, {'admin': True}) + # The new custom claims will propagate to the user's ID token the + # next time a new one is issued. # [END set_custom_user_claims] id_token = 'id_token' # [START verify_custom_claims] + # Verify the ID token first. claims = auth.verify_id_token(id_token) if claims['admin'] is True: # Allow access to requested admin resource. @@ -186,8 +190,10 @@ def set_custom_user_claims(uid): # [END verify_custom_claims] # [START read_custom_user_claims] + # Lookup the user associated with the specified uid. user = auth.get_user(uid) - print user.custom_claims + # The claims can be accessed on the user record. + print user.custom_claims.get('admin') # [END read_custom_user_claims] def set_custom_user_claims_script(): From 9283418684edec828155812835fdc4fb2ceaa8eb Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Wed, 7 Feb 2018 00:22:41 -0500 Subject: [PATCH 21/39] revoke, and check revoked --- auth/index.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/auth/index.py b/auth/index.py index 6e5ff10..2bf9f62 100644 --- a/auth/index.py +++ b/auth/index.py @@ -105,6 +105,40 @@ def verify_token_uid(id_token): print(uid) firebase_admin.delete_app(default_app) +def verify_token_uid_check_revoke(id_token): + cred = credentials.Certificate('path/to/service.json') + default_app = firebase_admin.initialize_app(cred) + # [START verify_token_id_check_revoked] + # id_token comes from the client app (shown above) + try: + decoded_token = auth.verify_id_token(id_token, + check_revoked=True) + except auth.AuthError as exc: + if exc.value.code == auth._ID_TOKEN_REVOKED: + pass + # When this occurs, inform the user to reauthenticate or signOut(). + uid = decoded_token['uid'] + # [END verify_token_id_check_revoked] + firebase_admin.delete_app(default_app) + return uid + +def revoke_refresh_token_uid(id_token): + cred = credentials.Certificate('path/to/service.json') + default_app = firebase_admin.initialize_app(cred) + # [START revoke_tokens] + # Revoke tokens on the backend. + auth.revoke_refresh_tokens(uid) + user = auth.get_user(uid) + # Convert to seconds as the auth_time in the token claims is in seconds. + revocation_second = user.tokens_valid_after_timestamp / 1000 + # Save the refresh token revocation timestamp. This is needed to track + # ID token revocation via Firebase rules. + metadata_ref = firebase_admin.db.reference("metadata/" + uid) + metadata_ref.set({'revokeTime': revocation_second}) + # [END revoke_tokens] + print(uid) + firebase_admin.delete_app(default_app) + def get_user(uid): # [START get_user] from firebase_admin import auth From fe991ea2c8da9461a54e5108e60786cb3fe2aa75 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Wed, 7 Feb 2018 12:53:51 -0500 Subject: [PATCH 22/39] clean comment --- auth/index.py | 1 - 1 file changed, 1 deletion(-) diff --git a/auth/index.py b/auth/index.py index 2bf9f62..a7bff9d 100644 --- a/auth/index.py +++ b/auth/index.py @@ -109,7 +109,6 @@ def verify_token_uid_check_revoke(id_token): cred = credentials.Certificate('path/to/service.json') default_app = firebase_admin.initialize_app(cred) # [START verify_token_id_check_revoked] - # id_token comes from the client app (shown above) try: decoded_token = auth.verify_id_token(id_token, check_revoked=True) From 83e67da8acfaecf364584475e6b4fbab20c8127c Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Wed, 7 Feb 2018 13:10:54 -0500 Subject: [PATCH 23/39] comment --- auth/index.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/auth/index.py b/auth/index.py index a7bff9d..74fa9f7 100644 --- a/auth/index.py +++ b/auth/index.py @@ -112,11 +112,12 @@ def verify_token_uid_check_revoke(id_token): try: decoded_token = auth.verify_id_token(id_token, check_revoked=True) + # Token is valid and not revoked. + uid = decoded_token['uid'] except auth.AuthError as exc: if exc.value.code == auth._ID_TOKEN_REVOKED: pass # When this occurs, inform the user to reauthenticate or signOut(). - uid = decoded_token['uid'] # [END verify_token_id_check_revoked] firebase_admin.delete_app(default_app) return uid From 6d306f102aa656a6e1f2e23430c4a66f1e68e355 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Wed, 7 Feb 2018 13:56:49 -0500 Subject: [PATCH 24/39] comments --- auth/index.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/auth/index.py b/auth/index.py index 74fa9f7..613109b 100644 --- a/auth/index.py +++ b/auth/index.py @@ -110,14 +110,16 @@ def verify_token_uid_check_revoke(id_token): default_app = firebase_admin.initialize_app(cred) # [START verify_token_id_check_revoked] try: + # Verify the ID token while checking if the token is revoked by + # passing check_revoked=True. decoded_token = auth.verify_id_token(id_token, check_revoked=True) # Token is valid and not revoked. uid = decoded_token['uid'] except auth.AuthError as exc: if exc.value.code == auth._ID_TOKEN_REVOKED: - pass # When this occurs, inform the user to reauthenticate or signOut(). + pass # [END verify_token_id_check_revoked] firebase_admin.delete_app(default_app) return uid From cfd19d212d8f180c28e056d7da6195a713d6f251 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Fri, 9 Feb 2018 16:38:40 -0500 Subject: [PATCH 25/39] fix error test --- auth/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/index.py b/auth/index.py index 613109b..3cac054 100644 --- a/auth/index.py +++ b/auth/index.py @@ -117,7 +117,7 @@ def verify_token_uid_check_revoke(id_token): # Token is valid and not revoked. uid = decoded_token['uid'] except auth.AuthError as exc: - if exc.value.code == auth._ID_TOKEN_REVOKED: + if exc.value.code == "ID_TOKEN_REVOKED": # When this occurs, inform the user to reauthenticate or signOut(). pass # [END verify_token_id_check_revoked] From 68d61f1a65d2d430f7d804a23d73ab16fe2dca9c Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Fri, 9 Feb 2018 16:53:53 -0500 Subject: [PATCH 26/39] single line --- auth/index.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/auth/index.py b/auth/index.py index 3cac054..1d9c6af 100644 --- a/auth/index.py +++ b/auth/index.py @@ -112,8 +112,7 @@ def verify_token_uid_check_revoke(id_token): try: # Verify the ID token while checking if the token is revoked by # passing check_revoked=True. - decoded_token = auth.verify_id_token(id_token, - check_revoked=True) + decoded_token = auth.verify_id_token(id_token, check_revoked=True) # Token is valid and not revoked. uid = decoded_token['uid'] except auth.AuthError as exc: From bd00396ae60256f45c81020cf48b329730c04572 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Fri, 9 Feb 2018 16:59:51 -0500 Subject: [PATCH 27/39] split db snippet --- auth/index.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/auth/index.py b/auth/index.py index 1d9c6af..1a9058c 100644 --- a/auth/index.py +++ b/auth/index.py @@ -132,11 +132,16 @@ def revoke_refresh_token_uid(id_token): user = auth.get_user(uid) # Convert to seconds as the auth_time in the token claims is in seconds. revocation_second = user.tokens_valid_after_timestamp / 1000 + # [END revoke_tokens] + # [START revoke_tokens_in_db] + user = auth.get_user(uid) + # Convert to seconds as the auth_time in the token claims is in seconds. + revocation_second = user.tokens_valid_after_timestamp / 1000 # Save the refresh token revocation timestamp. This is needed to track # ID token revocation via Firebase rules. metadata_ref = firebase_admin.db.reference("metadata/" + uid) metadata_ref.set({'revokeTime': revocation_second}) - # [END revoke_tokens] + # [END revoke_tokens_in_db] print(uid) firebase_admin.delete_app(default_app) From 5ceaddecaa46911c6d8ba9aeb068b786fcd6540c Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Fri, 9 Feb 2018 17:00:23 -0500 Subject: [PATCH 28/39] comment --- auth/index.py | 1 + 1 file changed, 1 insertion(+) diff --git a/auth/index.py b/auth/index.py index 1a9058c..0e468eb 100644 --- a/auth/index.py +++ b/auth/index.py @@ -134,6 +134,7 @@ def revoke_refresh_token_uid(id_token): revocation_second = user.tokens_valid_after_timestamp / 1000 # [END revoke_tokens] # [START revoke_tokens_in_db] + # After revoking tokens, follow up with: user = auth.get_user(uid) # Convert to seconds as the auth_time in the token claims is in seconds. revocation_second = user.tokens_valid_after_timestamp / 1000 From 6fc916e440ab26110a1a3d53e4e2188005e2eb3d Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Sat, 10 Feb 2018 05:43:27 -0500 Subject: [PATCH 29/39] comments --- auth/index.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/auth/index.py b/auth/index.py index 0e468eb..ce2c08a 100644 --- a/auth/index.py +++ b/auth/index.py @@ -134,12 +134,7 @@ def revoke_refresh_token_uid(id_token): revocation_second = user.tokens_valid_after_timestamp / 1000 # [END revoke_tokens] # [START revoke_tokens_in_db] - # After revoking tokens, follow up with: - user = auth.get_user(uid) - # Convert to seconds as the auth_time in the token claims is in seconds. - revocation_second = user.tokens_valid_after_timestamp / 1000 - # Save the refresh token revocation timestamp. This is needed to track - # ID token revocation via Firebase rules. + # After revoking refresh tokens, and retrieving the revocation time: metadata_ref = firebase_admin.db.reference("metadata/" + uid) metadata_ref.set({'revokeTime': revocation_second}) # [END revoke_tokens_in_db] From f006329a0bde5845eaf2d9a90a7a4b5a65c97e27 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Sat, 10 Feb 2018 05:44:56 -0500 Subject: [PATCH 30/39] comment --- auth/index.py | 1 - 1 file changed, 1 deletion(-) diff --git a/auth/index.py b/auth/index.py index ce2c08a..e70ecff 100644 --- a/auth/index.py +++ b/auth/index.py @@ -134,7 +134,6 @@ def revoke_refresh_token_uid(id_token): revocation_second = user.tokens_valid_after_timestamp / 1000 # [END revoke_tokens] # [START revoke_tokens_in_db] - # After revoking refresh tokens, and retrieving the revocation time: metadata_ref = firebase_admin.db.reference("metadata/" + uid) metadata_ref.set({'revokeTime': revocation_second}) # [END revoke_tokens_in_db] From 666945e3548e9ad7fe3740f59919f311e4aee30d Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Sat, 10 Feb 2018 05:48:16 -0500 Subject: [PATCH 31/39] tag change --- auth/index.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth/index.py b/auth/index.py index e70ecff..fc9b923 100644 --- a/auth/index.py +++ b/auth/index.py @@ -133,10 +133,10 @@ def revoke_refresh_token_uid(id_token): # Convert to seconds as the auth_time in the token claims is in seconds. revocation_second = user.tokens_valid_after_timestamp / 1000 # [END revoke_tokens] - # [START revoke_tokens_in_db] + # [START save_revocation_in_db] metadata_ref = firebase_admin.db.reference("metadata/" + uid) metadata_ref.set({'revokeTime': revocation_second}) - # [END revoke_tokens_in_db] + # [END save_revocation_in_db] print(uid) firebase_admin.delete_app(default_app) From 2e57e43f9a6b3eb714d5863b034e9c1392eed0a6 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 12 Feb 2018 20:40:47 -0500 Subject: [PATCH 32/39] single quotes --- auth/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/index.py b/auth/index.py index fc9b923..3c30633 100644 --- a/auth/index.py +++ b/auth/index.py @@ -116,7 +116,7 @@ def verify_token_uid_check_revoke(id_token): # Token is valid and not revoked. uid = decoded_token['uid'] except auth.AuthError as exc: - if exc.value.code == "ID_TOKEN_REVOKED": + if exc.value.code == 'ID_TOKEN_REVOKED': # When this occurs, inform the user to reauthenticate or signOut(). pass # [END verify_token_id_check_revoked] From 768ee23b4304250771a41dc01e305e1d19697eeb Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Tue, 13 Feb 2018 15:46:45 -0500 Subject: [PATCH 33/39] Fix logging and exception code in snippets --- auth/index.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/auth/index.py b/auth/index.py index 3c30633..e69c550 100644 --- a/auth/index.py +++ b/auth/index.py @@ -116,7 +116,7 @@ def verify_token_uid_check_revoke(id_token): # Token is valid and not revoked. uid = decoded_token['uid'] except auth.AuthError as exc: - if exc.value.code == 'ID_TOKEN_REVOKED': + if exc.code == 'ID_TOKEN_REVOKED': # When this occurs, inform the user to reauthenticate or signOut(). pass # [END verify_token_id_check_revoked] @@ -132,6 +132,7 @@ def revoke_refresh_token_uid(id_token): user = auth.get_user(uid) # Convert to seconds as the auth_time in the token claims is in seconds. revocation_second = user.tokens_valid_after_timestamp / 1000 + print 'Tokens revoked at: {0}'.format(revocation_second) # [END revoke_tokens] # [START save_revocation_in_db] metadata_ref = firebase_admin.db.reference("metadata/" + uid) From 00b7830d17a0182c41c40f3b76705f9df8969906 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Wed, 14 Feb 2018 17:09:07 -0500 Subject: [PATCH 34/39] Unifying check revoked comments --- auth/index.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/auth/index.py b/auth/index.py index e69c550..55be033 100644 --- a/auth/index.py +++ b/auth/index.py @@ -117,7 +117,10 @@ def verify_token_uid_check_revoke(id_token): uid = decoded_token['uid'] except auth.AuthError as exc: if exc.code == 'ID_TOKEN_REVOKED': - # When this occurs, inform the user to reauthenticate or signOut(). + # Token revoked, inform the user to reauthenticate or signOut(). + pass + else: + # Token is invalid pass # [END verify_token_id_check_revoked] firebase_admin.delete_app(default_app) From d03976ccfd5e7e33554a4793593bc27222169072 Mon Sep 17 00:00:00 2001 From: Sam Stern Date: Thu, 22 Feb 2018 13:23:57 -0800 Subject: [PATCH 35/39] Remove auth and db snippets (#10) These snippets have been moved to firebase-admin-python --- auth/index.py | 295 ------------------------------------------- db/index.py | 344 -------------------------------------------------- 2 files changed, 639 deletions(-) delete mode 100644 auth/index.py delete mode 100644 db/index.py diff --git a/auth/index.py b/auth/index.py deleted file mode 100644 index 55be033..0000000 --- a/auth/index.py +++ /dev/null @@ -1,295 +0,0 @@ -import sys -sys.path.append("lib") - -# [START import_sdk] -import firebase_admin -# [END import_sdk] -from firebase_admin import credentials -from firebase_admin import auth - -def initialize_sdk_with_service_account(): - # [START initialize_sdk_with_service_account] - import firebase_admin - from firebase_admin import credentials - - cred = credentials.Certificate('path/to/serviceAccountKey.json') - default_app = firebase_admin.initialize_app(cred) - # [END initialize_sdk_with_service_account] - firebase_admin.delete_app(default_app) - -def initialize_sdk_with_application_default(): - # [START initialize_sdk_with_application_default] - default_app = firebase_admin.initialize_app() - # [END initialize_sdk_with_application_default] - firebase_admin.delete_app(default_app) - -def initialize_sdk_with_refresh_token(): - # [START initialize_sdk_with_refresh_token] - cred = credentials.RefreshToken('path/to/refreshToken.json') - default_app = firebase_admin.initialize_app(cred) - # [END initialize_sdk_with_refresh_token] - firebase_admin.delete_app(default_app) - -def access_services_default(): - cred = credentials.Certificate('path/to/service.json') - # [START access_services_default] - # Import the Firebase service - from firebase_admin import auth - - # Initialize the default app - default_app = firebase_admin.initialize_app(cred) - print(default_app.name); # "[DEFAULT]" - - # Retrieve services via the auth package... - # auth.create_custom_token(...) - # [END access_services_default] - firebase_admin.delete_app(default_app) - -def access_services_nondefault(): - cred = credentials.Certificate('path/to/service.json') - otherCred = credentials.Certificate('path/to/service.json') - - # [START access_services_nondefault] - # Initialize the default app - default_app = firebase_admin.initialize_app(cred) - - # Initialize another app with a different config - other_app = firebase_admin.initialize_app(cred, name='other') - - print(default_app.name); # "[DEFAULT]" - print(other_app.name); # "other" - - # Retrieve default services via the auth package... - # auth.create_custom_token(...) - - # Use the `app` argument to retrieve the other app's services - # auth.create_custom_token(..., app=other_app) - # [END access_services_nondefault] - firebase_admin.delete_app(default_app) - firebase_admin.delete_app(other_app) - -def create_token_uid(): - cred = credentials.Certificate('path/to/service.json') - default_app = firebase_admin.initialize_app(cred) - # [START create_token_uid] - uid = 'some-uid' - - custom_token = auth.create_custom_token(uid) - # [END create_token_uid] - firebase_admin.delete_app(default_app) - return custom_token - -def create_token_with_claims(): - cred = credentials.Certificate('path/to/service.json') - default_app = firebase_admin.initialize_app(cred) - # [START create_token_with_claims] - uid = 'some-uid' - additional_claims = { - 'premiumAccount': True - } - - custom_token = auth.create_custom_token(uid, additional_claims) - # [END create_token_with_claims] - firebase_admin.delete_app(default_app) - return custom_token - -def verify_token_uid(id_token): - cred = credentials.Certificate('path/to/service.json') - default_app = firebase_admin.initialize_app(cred) - # [START verify_token_uid] - # id_token comes from the client app (shown above) - - decoded_token = auth.verify_id_token(id_token) - uid = decoded_token['uid'] - # [END verify_token_uid] - print(uid) - firebase_admin.delete_app(default_app) - -def verify_token_uid_check_revoke(id_token): - cred = credentials.Certificate('path/to/service.json') - default_app = firebase_admin.initialize_app(cred) - # [START verify_token_id_check_revoked] - try: - # Verify the ID token while checking if the token is revoked by - # passing check_revoked=True. - decoded_token = auth.verify_id_token(id_token, check_revoked=True) - # Token is valid and not revoked. - uid = decoded_token['uid'] - except auth.AuthError as exc: - if exc.code == 'ID_TOKEN_REVOKED': - # Token revoked, inform the user to reauthenticate or signOut(). - pass - else: - # Token is invalid - pass - # [END verify_token_id_check_revoked] - firebase_admin.delete_app(default_app) - return uid - -def revoke_refresh_token_uid(id_token): - cred = credentials.Certificate('path/to/service.json') - default_app = firebase_admin.initialize_app(cred) - # [START revoke_tokens] - # Revoke tokens on the backend. - auth.revoke_refresh_tokens(uid) - user = auth.get_user(uid) - # Convert to seconds as the auth_time in the token claims is in seconds. - revocation_second = user.tokens_valid_after_timestamp / 1000 - print 'Tokens revoked at: {0}'.format(revocation_second) - # [END revoke_tokens] - # [START save_revocation_in_db] - metadata_ref = firebase_admin.db.reference("metadata/" + uid) - metadata_ref.set({'revokeTime': revocation_second}) - # [END save_revocation_in_db] - print(uid) - firebase_admin.delete_app(default_app) - -def get_user(uid): - # [START get_user] - from firebase_admin import auth - - user = auth.get_user(uid) - print 'Successfully fetched user data: {0}'.format(user.uid) - # [END get_user] - -def get_user_by_email(): - email = 'user@example.com' - # [START get_user_by_email] - from firebase_admin import auth - - user = auth.get_user_by_email(email) - print 'Successfully fetched user data: {0}'.format(user.uid) - # [END get_user_by_email] - -def get_user_by_phone_number(): - phone = '+1 555 555 0100' - # [START get_user_by_phone] - from firebase_admin import auth - - user = auth.get_user_by_phone_number(phone) - print 'Successfully fetched user data: {0}'.format(user.uid) - # [END get_user_by_phone] - -def create_user(): - # [START create_user] - user = auth.create_user( - email='user@example.com', - email_verified=False, - phone_number='+15555550100', - password='secretPassword', - display_name='John Doe', - photo_url='http://www.example.com/12345678/photo.png', - disabled=False) - print 'Sucessfully created new user: {0}'.format(user.uid) - # [END create_user] - return user.uid - -def create_user_with_id(): - # [START create_user_with_id] - user = auth.create_user( - uid='some-uid', email='user@example.com', phone_number='+15555550100') - print 'Sucessfully created new user: {0}'.format(user.uid) - # [END create_user_with_id] - -def update_user(uid): - # [START update_user] - user = auth.update_user( - uid, - email='user@example.com', - phone_number='+15555550100', - email_verified=True, - password='newPassword', - display_name='John Doe', - photo_url='http://www.example.com/12345678/photo.png', - disabled=True) - print 'Sucessfully updated user: {0}'.format(user.uid) - # [END update_user] - -def delete_user(uid): - # [START delete_user] - auth.delete_user(uid) - print 'Successfully deleted user' - # [END delete_user] - -def set_custom_user_claims(uid): - # [START set_custom_user_claims] - # Set admin privilege on the user corresponding to uid. - auth.set_custom_user_claims(uid, {'admin': True}) - # The new custom claims will propagate to the user's ID token the - # next time a new one is issued. - # [END set_custom_user_claims] - - id_token = 'id_token' - # [START verify_custom_claims] - # Verify the ID token first. - claims = auth.verify_id_token(id_token) - if claims['admin'] is True: - # Allow access to requested admin resource. - pass - # [END verify_custom_claims] - - # [START read_custom_user_claims] - # Lookup the user associated with the specified uid. - user = auth.get_user(uid) - # The claims can be accessed on the user record. - print user.custom_claims.get('admin') - # [END read_custom_user_claims] - -def set_custom_user_claims_script(): - # [START set_custom_user_claims_script] - user = auth.get_user_by_email('user@admin.example.com') - # Confirm user is verified - if user.email_verified: - # Add custom claims for additional privileges. - # This will be picked up by the user on token refresh or next sign in on new device. - auth.set_custom_user_claims(user.uid, { - 'admin': True - }) - # [END set_custom_user_claims_script] - -def set_custom_user_claims_incremental(): - # [START set_custom_user_claims_incremental] - user = auth.get_user_by_email('user@admin.example.com') - # Add incremental custom claim without overwriting existing claims. - current_custom_claims = user.custom_claims - if current_custom_claims.get('admin'): - # Add level. - current_custom_claims['accessLevel'] = 10 - # Add custom claims for additional privileges. - auth.set_custom_user_claims(user.uid, current_custom_claims) - # [END set_custom_user_claims_incremental] - -def list_all_users(): - # [START list_all_users] - # Start listing users from the beginning, 1000 at a time. - page = auth.list_users() - while page: - for user in page.users: - print 'User: ' + user.uid - # Get next batch of users. - page = page.get_next_page() - - # Iterate through all users. This will still retrieve users in batches, - # buffering no more than 1000 users in memory at a time. - for user in auth.list_users().iterate_all(): - print 'User: ' + user.uid - # [END list_all_users] - -initialize_sdk_with_service_account() -initialize_sdk_with_application_default() -#initialize_sdk_with_refresh_token() -access_services_default() -access_services_nondefault() -create_token_uid() -token_with_claims = create_token_with_claims() -#verify_token_uid() - -uid = create_user() -create_user_with_id() -get_user(uid) -get_user_by_email() -get_user_by_phone_number() -update_user(uid) -set_custom_user_claims(uid) -list_all_users() -delete_user(uid) diff --git a/db/index.py b/db/index.py deleted file mode 100644 index d58f0f7..0000000 --- a/db/index.py +++ /dev/null @@ -1,344 +0,0 @@ -import firebase_admin -from firebase_admin import credentials -from firebase_admin import db - -def authenticate_with_admin_privileges(): - # [START authenticate_with_admin_privileges] - import firebase_admin - from firebase_admin import credentials - from firebase_admin import db - - # Fetch the service account key JSON file contents - cred = credentials.Certificate('path/to/serviceAccountKey.json') - - # Initialize the app with a service account, granting admin privileges - firebase_admin.initialize_app(cred, { - 'databaseURL': 'https://databaseName.firebaseio.com' - }) - - # As an admin, the app has access to read and write all data, regradless of Security Rules - ref = db.reference('restricted_access/secret_document') - print(ref.get()) - # [END authenticate_with_admin_privileges] - firebase_admin.delete_app(firebase_admin.get_app()) - -def authenticate_with_limited_privileges(): - # [START authenticate_with_limited_privileges] - import firebase_admin - from firebase_admin import credentials - from firebase_admin import db - - # Fetch the service account key JSON file contents - cred = credentials.Certificate('path/to/serviceAccountKey.json') - - # Initialize the app with a custom auth variable, limiting the server's access - firebase_admin.initialize_app(cred, { - 'databaseURL': 'https://databaseName.firebaseio.com', - 'databaseAuthVariableOverride': { - 'uid': 'my-service-worker' - } - }) - - # The app only has access as defined in the Security Rules - ref = db.reference('/some_resource') - print(ref.get()) - # [END authenticate_with_limited_privileges] - firebase_admin.delete_app(firebase_admin.get_app()) - -def authenticate_with_guest_privileges(): - # [START authenticate_with_guest_privileges] - import firebase_admin - from firebase_admin import credentials - from firebase_admin import db - - # Fetch the service account key JSON file contents - cred = credentials.Certificate('path/to/serviceAccountKey.json') - - # Initialize the app with a None auth variable, limiting the server's access - firebase_admin.initialize_app(cred, { - 'databaseURL': 'https://databaseName.firebaseio.com', - 'databaseAuthVariableOverride': None - }) - - # The app only has access to public data as defined in the Security Rules - ref = db.reference('/public_resource') - print(ref.get()) - # [END authenticate_with_guest_privileges] - firebase_admin.delete_app(firebase_admin.get_app()) - -def get_reference(): - # [START get_reference] - # Import database module. - from firebase_admin import db - - # Get a database reference to our blog. - ref = db.reference('server/saving-data/fireblog') - # [END get_reference] - print(ref.key) - -def set_value(): - ref = db.reference('server/saving-data/fireblog') - - # [START set_value] - users_ref = ref.child('users') - users_ref.set({ - 'alanisawesome': { - 'date_of_birth': 'June 23, 1912', - 'full_name': 'Alan Turing' - }, - 'gracehop': { - 'date_of_birth': 'December 9, 1906', - 'full_name': 'Grace Hopper' - } - }) - # [END set_value] - -def set_child_value(): - ref = db.reference('server/saving-data/fireblog') - users_ref = ref.child('users') - - # [START set_child_value] - users_ref.child('alanisawesome').set({ - 'date_of_birth': 'June 23, 1912', - 'full_name': 'Alan Turing' - }) - users_ref.child('gracehop').set({ - 'date_of_birth': 'December 9, 1906', - 'full_name': 'Grace Hopper' - }) - # [END set_child_value] - -def update_child(): - ref = db.reference('server/saving-data/fireblog') - users_ref = ref.child('users') - - # [START update_child] - hopper_ref = users_ref.child('gracehop') - hopper_ref.update({ - 'nickname': 'Amazing Grace' - }) - # [END update_child] - -def update_children(): - ref = db.reference('server/saving-data/fireblog') - users_ref = ref.child('users') - - # [START update_children] - users_ref.update({ - 'alanisawesome/nickname': 'Alan The Machine', - 'gracehop/nickname': 'Amazing Grace' - }) - # [END update_children] - -def overwrite_value(): - ref = db.reference('server/saving-data/fireblog') - users_ref = ref.child('users') - - # [START overwrite_value] - users_ref.update({ - 'alanisawesome': { - 'nickname': 'Alan The Machine' - }, - 'gracehop': { - 'nickname': 'Amazing Grace' - } - }) - # [END overwrite_value] - -def push_value(): - ref = db.reference('server/saving-data/fireblog') - - # [START push_value] - posts_ref = ref.child('posts') - - new_post_ref = posts_ref.push() - new_post_ref.set({ - 'author': 'gracehop', - 'title': 'Announcing COBOL, a New Programming Language' - }) - - # We can also chain the two calls together - posts_ref.push().set({ - 'author': 'alanisawesome', - 'title': 'The Turing Machine' - }) - # [END push_value] - -def push_and_set_value(): - ref = db.reference('server/saving-data/fireblog') - posts_ref = ref.child('posts') - - # [START push_and_set_value] - # This is equivalent to the calls to push().set(...) above - posts_ref.push({ - 'author': 'gracehop', - 'title': 'Announcing COBOL, a New Programming Language' - }) - # [END push_and_set_value] - -def get_push_key(): - ref = db.reference('server/saving-data/fireblog') - posts_ref = ref.child('posts') - - # [START push_key] - # Generate a reference to a new location and add some data using push() - new_post_ref = posts_ref.push() - - # Get the unique key generated by push() - post_id = new_post_ref.key - # [END push_key] - print(post_id) - -def run_transaction(): - # [START transaction] - def increment_votes(current_value): - return current_value + 1 if current_value else 1 - - upvotes_ref = db.reference('server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes') - try: - new_vote_count = upvotes_ref.transaction(increment_votes) - print 'Transaction completed' - except db.TransactionError: - print 'Transaction failed to commit' - # [END transaction] - -def read_value(): - # [START read_value] - # Import database module. - from firebase_admin import db - - # Get a database reference to our posts - ref = db.reference('server/saving-data/fireblog/posts') - - # Read the data at the posts reference (this is a blocking operation) - print(ref.get()) - # [END read_value] - -def order_by_child(): - # [START order_by_child] - ref = db.reference('dinosaurs') - snapshot = ref.order_by_child('height').get() - for key, val in snapshot.items(): - print('{0} was {1} meters tall'.format(key, val)) - # [END order_by_child] - -def order_by_nested_child(): - # [START order_by_nested_child] - ref = db.reference('dinosaurs') - snapshot = ref.order_by_child('dimensions/height').get() - for key, val in snapshot.items(): - print('{0} was {1} meters tall'.format(key, val)) - # [END order_by_nested_child] - -def order_by_key(): - # [START order_by_key] - ref = db.reference('dinosaurs') - snapshot = ref.order_by_key().get() - print(snapshot) - # [END order_by_key] - -def order_by_value(): - # [START order_by_value] - ref = db.reference('scores') - snapshot = ref.order_by_value().get() - for key, val in snapshot.items(): - print('The {0} dinosaur\'s score is {1}'.format(key, val)) - # [END order_by_value] - -def limit_query(): - # [START limit_query_1] - ref = db.reference('dinosaurs') - snapshot = ref.order_by_child('weight').limit_to_last(2).get() - for key in snapshot: - print(key) - # [END limit_query_1] - - # [START limit_query_2] - ref = db.reference('dinosaurs') - snapshot = ref.order_by_child('height').limit_to_first(2).get() - for key in snapshot: - print(key) - # [END limit_query_2] - - # [START limit_query_3] - scores_ref = db.reference('scores') - snapshot = scores_ref.order_by_value().limit_to_last(3).get() - for key, val in snapshot.items(): - print('The {0} dinosaur\'s score is {1}'.format(key, val)) - # [END limit_query_3] - -def range_query(): - # [START range_query_1] - ref = db.reference('dinosaurs') - snapshot = ref.order_by_child('height').start_at(3).get() - for key in snapshot: - print(key) - # [END range_query_1] - - # [START range_query_2] - ref = db.reference('dinosaurs') - snapshot = ref.order_by_key().end_at('pterodactyl').get() - for key in snapshot: - print(key) - # [END range_query_2] - - # [START range_query_3] - ref = db.reference('dinosaurs') - snapshot = ref.order_by_key().start_at('b').end_at(u'b\uf8ff').get() - for key in snapshot: - print(key) - # [END range_query_3] - - # [START range_query_4] - ref = db.reference('dinosaurs') - snapshot = ref.order_by_child('height').equal_to(25).get() - for key in snapshot: - print(key) - # [END range_query_4] - -def complex_query(): - # [START complex_query] - ref = db.reference('dinosaurs') - favotire_dino_height = ref.child('stegosaurus').child('height').get() - query = ref.order_by_child('height').end_at(favotire_dino_height).limit_to_last(2) - snapshot = query.get() - if len(snapshot) == 2: - # Data is ordered by increasing height, so we want the first entry. - # Second entry is stegosarus. - for key in snapshot: - print('The dinosaur just shorter than the stegosaurus is {0}'.format(key)) - return - else: - print('The stegosaurus is the shortest dino') - # [END complex_query] - - -service_account = 'path/to/serviceAccount.json' -database_url = 'https://databaseName.firebaseio.com' - -cred = credentials.Certificate(service_account) -firebase_admin.initialize_app(cred, { - 'databaseURL': database_url -}) - -get_reference() -set_value() -set_child_value() -update_child() -update_children() -overwrite_value() -push_value() -push_and_set_value() -get_push_key() -run_transaction() - -read_value() -order_by_child() -#order_by_nested_child() -order_by_key() -order_by_value() -limit_query() -range_query() -complex_query() - -firebase_admin.delete_app(firebase_admin.get_app()) From 5623cc957a12a8a2fc96b72cd6323f4dc8c69204 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Thu, 20 Jul 2017 15:56:57 -0700 Subject: [PATCH 36/39] Add Remote Config Sample Add sample to fetch and publish template. Change-Id: I22bb599a311a9111eeb15b9d758280f96b77899b --- config/.gitignore | 3 ++ config/configure.py | 87 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 config/.gitignore create mode 100644 config/configure.py diff --git a/config/.gitignore b/config/.gitignore new file mode 100644 index 0000000..4813827 --- /dev/null +++ b/config/.gitignore @@ -0,0 +1,3 @@ +config.json +service-account.json +.idea \ No newline at end of file diff --git a/config/configure.py b/config/configure.py new file mode 100644 index 0000000..7344d2d --- /dev/null +++ b/config/configure.py @@ -0,0 +1,87 @@ +import argparse +import requests + +from oauth2client.service_account import ServiceAccountCredentials + + +PROJECT_ID = '' +BASE_URL = 'https://www.googleapis.com' +REMOTE_CONFIG_ENDPOINT = 'v1/projects/' + PROJECT_ID + '/remoteConfig' +REMOTE_CONFIG_URL = BASE_URL + '/' + REMOTE_CONFIG_ENDPOINT +REMOTE_CONFIG_SCOPE = 'https://www.googleapis.com/auth/firebase.remoteconfig' + + +def _get_access_token(): + """Retrieve a valid access token that can be used to authorize requests. + + :return: Access token. + """ + credentials = ServiceAccountCredentials.from_json_keyfile_name( + 'service-account.json', REMOTE_CONFIG_SCOPE) + access_token_info = credentials.get_access_token() + return access_token_info.access_token + +def _fetch(): + """Fetch the current Firebase Remote Config template from server. + + Fetch the current Firebase Remote Config template from server and store it + locally. + """ + headers = { + 'Authorization': 'Bearer ' + _get_access_token() + } + resp = requests.get(REMOTE_CONFIG_URL, headers=headers) + + if resp.status_code == 200: + with open('config.json', 'w') as f: + f.write(resp.text) + + print('Fetched template has been written to config.json') + print('ETag from server: {}'.format(resp.headers['X-Google-API-ETag'])) + else: + print('Unable to fetch template') + print(resp.text) + + +def _publish(etag): + """Publish local template to Firebase server. + + Args: + etag: ETag for safe (avoid race conditions) template updates. + * can be used to force template replacement. + """ + with open('config.json', 'r') as f: + content = f.read() + headers = { + 'Authorization': 'Bearer ' + _get_access_token(), + 'Content-Type': 'application/json; UTF-8', + 'If-Match': etag + } + resp = requests.put(REMOTE_CONFIG_URL, data=content, headers=headers) + if resp.status_code == 200: + print('Template has been published.') + print('ETag from server: {}'.format(resp.headers['X-Google-API-ETag'])) + else: + print('Unable to publish template.') + print(resp.text) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--action') + parser.add_argument('--etag') + args = parser.parse_args() + + if args.action and args.action == 'fetch': + _fetch() + elif args.action and args.action == 'publish' and args.etag: + _publish(args.etag) + else: + print('''Invalid command. Please use one of the following commands: +python configure.py --action=fetch +python configure.py --action=publish --etag=''') + + + +if __name__ == '__main__': + main() From 198f4435869e45cc7146c1d93c3a5fd635a6258c Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Tue, 25 Jul 2017 15:45:12 -0700 Subject: [PATCH 37/39] Add README Change-Id: I8c07d7b41fe7220379d159a099ecc19fe2eaf5eb --- config/README.md | 85 +++++++++++++++++++++++++++++++++++++++++++++ config/configure.py | 22 ++++++------ 2 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 config/README.md diff --git a/config/README.md b/config/README.md new file mode 100644 index 0000000..772a25f --- /dev/null +++ b/config/README.md @@ -0,0 +1,85 @@ +Firebase Remote Config REST API Python Quickstart +=============================================== + +The Firebase Remote Config Python quickstart app demonstrates retrieving and +updating the Firebase Remote Config template. + +Introduction +------------ + +This is a simple example of using the Firebase Remote Config REST API to update +the Remote Config template being used by clients apps. + +Getting started +--------------- + +1. [Add Firebase to your Android Project](https://firebase.google.com/docs/android/setup). +2. Create a service account as described in [Adding Firebase to your Server](https://firebase.google.com/docs/admin/setup) and download the JSON file. + - Copy the private key JSON file to this folder and rename it to `service-account.json`. +3. Change the `PROJECT_ID` variable in `configure.py` to your project ID. + +Run +--- + +- From the `config` directory run `python configure.py --action=get` to retrieve the template. + - The returned template is stored in a file named `config.json`. + - Note the Etag printed to the console you will need to use it when publishing template updates. +- Update the template. + - If your template already has parameters, adjust one or more of the values. + - If your template is empty, update it to look like this: + + { + "conditions": [ + { + "name": "AndroidUsers", + "expression": "device.os == 'android'", + "tagColor": "PURPLE" + }, + { + "name": "iOSUsers", + "expression": "device.os == 'ios'", + "tagColor": "GREEN" + } + ], + "parameters": { + "welcome_message": { + "defaultValue": { + "value": "Welcome" + }, + "conditionalValues": { + "AndroidUsers": { + "value": "Welcome Android User" + }, + "iOSUsers": { + "value": "Welcome iOS User" + } + } + } + } + } + +- From the `config` directory run `python configure.py --action=publish --etag=` to update the template. + - Be sure to set the etag to the one that was last printed in the console. +- Confirm in the console that the template has been updated. + - At this point mobile clients can fetch the updated values. + +Best practices +-------------- + +This section provides some additional information about how the Remote Config +REST API should be used when retrieving and updating templates. + +### Etags ### + +Eath time the Remote Config template is retrieved an Etag is included. This Etag is a +unique identifier of the current template on the server. When submitting updates +to the template you must include the latest Etag to ensure that your updates are consistent. + +In the event that you want to completely overwrite the server's template use +an Etag of "\*". Use this with caution since this operation cannot be undone. + +Support +------- + +- [Stack Overflow](https://stackoverflow.com/questions/tagged/firebase-cloud-messaging) +- [Firebase Support](https://firebase.google.com/support/) diff --git a/config/configure.py b/config/configure.py index 7344d2d..6a5c012 100644 --- a/config/configure.py +++ b/config/configure.py @@ -5,7 +5,7 @@ PROJECT_ID = '' -BASE_URL = 'https://www.googleapis.com' +BASE_URL = 'https://firebaseremoteconfig.googleapis.com' REMOTE_CONFIG_ENDPOINT = 'v1/projects/' + PROJECT_ID + '/remoteConfig' REMOTE_CONFIG_URL = BASE_URL + '/' + REMOTE_CONFIG_ENDPOINT REMOTE_CONFIG_SCOPE = 'https://www.googleapis.com/auth/firebase.remoteconfig' @@ -21,10 +21,10 @@ def _get_access_token(): access_token_info = credentials.get_access_token() return access_token_info.access_token -def _fetch(): - """Fetch the current Firebase Remote Config template from server. +def _get(): + """Retrieve the current Firebase Remote Config template from server. - Fetch the current Firebase Remote Config template from server and store it + Retrieve the current Firebase Remote Config template from server and store it locally. """ headers = { @@ -36,10 +36,10 @@ def _fetch(): with open('config.json', 'w') as f: f.write(resp.text) - print('Fetched template has been written to config.json') - print('ETag from server: {}'.format(resp.headers['X-Google-API-ETag'])) + print('Retrieved template has been written to config.json') + print('ETag from server: {}'.format(resp.headers['ETag'])) else: - print('Unable to fetch template') + print('Unable to get template') print(resp.text) @@ -60,7 +60,7 @@ def _publish(etag): resp = requests.put(REMOTE_CONFIG_URL, data=content, headers=headers) if resp.status_code == 200: print('Template has been published.') - print('ETag from server: {}'.format(resp.headers['X-Google-API-ETag'])) + print('ETag from server: {}'.format(resp.headers['ETag'])) else: print('Unable to publish template.') print(resp.text) @@ -72,13 +72,13 @@ def main(): parser.add_argument('--etag') args = parser.parse_args() - if args.action and args.action == 'fetch': - _fetch() + if args.action and args.action == 'get': + _get() elif args.action and args.action == 'publish' and args.etag: _publish(args.etag) else: print('''Invalid command. Please use one of the following commands: -python configure.py --action=fetch +python configure.py --action=get python configure.py --action=publish --etag=''') From 7a539d86ac97436c0b617e3155b9e515f3e5d653 Mon Sep 17 00:00:00 2001 From: deveshmittalpaytm <37627364+deveshmittalpaytm@users.noreply.github.com> Date: Wed, 28 Mar 2018 02:54:19 +0530 Subject: [PATCH 38/39] Added support for UTF-8 (#13) * Added support for UTF-8 --- config/configure.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/config/configure.py b/config/configure.py index 6a5c012..ffa0de5 100644 --- a/config/configure.py +++ b/config/configure.py @@ -1,5 +1,6 @@ import argparse import requests +import io from oauth2client.service_account import ServiceAccountCredentials @@ -33,8 +34,8 @@ def _get(): resp = requests.get(REMOTE_CONFIG_URL, headers=headers) if resp.status_code == 200: - with open('config.json', 'w') as f: - f.write(resp.text) + with io.open('config.json', 'wb') as f: + f.write(resp.text.encode('utf-8')) print('Retrieved template has been written to config.json') print('ETag from server: {}'.format(resp.headers['ETag'])) @@ -50,14 +51,14 @@ def _publish(etag): etag: ETag for safe (avoid race conditions) template updates. * can be used to force template replacement. """ - with open('config.json', 'r') as f: + with open('config.json', 'r', encoding='utf-8') as f: content = f.read() headers = { 'Authorization': 'Bearer ' + _get_access_token(), 'Content-Type': 'application/json; UTF-8', 'If-Match': etag } - resp = requests.put(REMOTE_CONFIG_URL, data=content, headers=headers) + resp = requests.put(REMOTE_CONFIG_URL, data=content.encode('utf-8'), headers=headers) if resp.status_code == 200: print('Template has been published.') print('ETag from server: {}'.format(resp.headers['ETag'])) @@ -84,4 +85,4 @@ def main(): if __name__ == '__main__': - main() + main() \ No newline at end of file From 7840f0c47d727e8fa436319474f69da55f616094 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Wed, 23 May 2018 08:27:28 -0700 Subject: [PATCH 39/39] Show possible use of multiple scopes --- config/configure.py | 7 ++++--- messaging/messaging.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/config/configure.py b/config/configure.py index ffa0de5..dc4f81c 100644 --- a/config/configure.py +++ b/config/configure.py @@ -9,18 +9,19 @@ BASE_URL = 'https://firebaseremoteconfig.googleapis.com' REMOTE_CONFIG_ENDPOINT = 'v1/projects/' + PROJECT_ID + '/remoteConfig' REMOTE_CONFIG_URL = BASE_URL + '/' + REMOTE_CONFIG_ENDPOINT -REMOTE_CONFIG_SCOPE = 'https://www.googleapis.com/auth/firebase.remoteconfig' - +SCOPES = ['https://www.googleapis.com/auth/firebase.remoteconfig'] +# [START retrieve_access_token] def _get_access_token(): """Retrieve a valid access token that can be used to authorize requests. :return: Access token. """ credentials = ServiceAccountCredentials.from_json_keyfile_name( - 'service-account.json', REMOTE_CONFIG_SCOPE) + 'service-account.json', SCOPES) access_token_info = credentials.get_access_token() return access_token_info.access_token +# [END retrieve_access_token] def _get(): """Retrieve the current Firebase Remote Config template from server. diff --git a/messaging/messaging.py b/messaging/messaging.py index a5ef396..3f1c039 100644 --- a/messaging/messaging.py +++ b/messaging/messaging.py @@ -19,7 +19,7 @@ BASE_URL = 'https://fcm.googleapis.com' FCM_ENDPOINT = 'v1/projects/' + PROJECT_ID + '/messages:send' FCM_URL = BASE_URL + '/' + FCM_ENDPOINT -FCM_SCOPE = 'https://www.googleapis.com/auth/firebase.messaging' +SCOPES = ['https://www.googleapis.com/auth/firebase.messaging'] # [START retrieve_access_token] def _get_access_token(): @@ -28,7 +28,7 @@ def _get_access_token(): :return: Access token. """ credentials = ServiceAccountCredentials.from_json_keyfile_name( - 'service-account.json', FCM_SCOPE) + 'service-account.json', SCOPES) access_token_info = credentials.get_access_token() return access_token_info.access_token # [END retrieve_access_token]