cone.firebase 0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cone_firebase-0.1/CHANGES.rst +7 -0
- cone_firebase-0.1/LICENSE.rst +27 -0
- cone_firebase-0.1/MANIFEST.in +3 -0
- cone_firebase-0.1/PKG-INFO +146 -0
- cone_firebase-0.1/README.rst +64 -0
- cone_firebase-0.1/setup.cfg +4 -0
- cone_firebase-0.1/setup.py +72 -0
- cone_firebase-0.1/src/cone/__init__.py +1 -0
- cone_firebase-0.1/src/cone/firebase/__init__.py +87 -0
- cone_firebase-0.1/src/cone/firebase/api.py +5 -0
- cone_firebase-0.1/src/cone/firebase/authentication.py +76 -0
- cone_firebase-0.1/src/cone/firebase/management.py +181 -0
- cone_firebase-0.1/src/cone/firebase/messaging.py +52 -0
- cone_firebase-0.1/src/cone/firebase/testing/__init__.py +133 -0
- cone_firebase-0.1/src/cone/firebase/testing/firebase_admin/__init__.py +54 -0
- cone_firebase-0.1/src/cone/firebase/testing/firebase_admin/credentials.py +4 -0
- cone_firebase-0.1/src/cone/firebase/testing/firebase_admin/messaging.py +121 -0
- cone_firebase-0.1/src/cone/firebase/tests.py +112 -0
- cone_firebase-0.1/src/cone.firebase.egg-info/PKG-INFO +146 -0
- cone_firebase-0.1/src/cone.firebase.egg-info/SOURCES.txt +23 -0
- cone_firebase-0.1/src/cone.firebase.egg-info/dependency_links.txt +1 -0
- cone_firebase-0.1/src/cone.firebase.egg-info/namespace_packages.txt +1 -0
- cone_firebase-0.1/src/cone.firebase.egg-info/not-zip-safe +1 -0
- cone_firebase-0.1/src/cone.firebase.egg-info/requires.txt +20 -0
- cone_firebase-0.1/src/cone.firebase.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
License
|
|
2
|
+
=======
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2021, BlueDynamics Alliance, Austria
|
|
5
|
+
Copyright (c) 2021, Cone Contributors
|
|
6
|
+
All rights reserved.
|
|
7
|
+
|
|
8
|
+
Redistribution and use in source and binary forms, with or without
|
|
9
|
+
modification, are permitted provided that the following conditions are met:
|
|
10
|
+
|
|
11
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
12
|
+
list of conditions and the following disclaimer.
|
|
13
|
+
|
|
14
|
+
* Redistributions in binary form must reproduce the above copyright notice, this
|
|
15
|
+
list of conditions and the following disclaimer in the documentation and/or
|
|
16
|
+
other materials provided with the distribution.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
22
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
25
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cone.firebase
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: Firebase integration for cone.app
|
|
5
|
+
Home-page: http://github.com/conestack/cone.firebase
|
|
6
|
+
Author: Cone Contributors
|
|
7
|
+
Author-email: dev@conestack.org
|
|
8
|
+
License: Simplified BSD
|
|
9
|
+
Keywords: node pyramid cone web
|
|
10
|
+
Classifier: Environment :: Web Environment
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
13
|
+
License-File: LICENSE.rst
|
|
14
|
+
Requires-Dist: proto-plus==1.18.1
|
|
15
|
+
Requires-Dist: google-api-core==1.26.3
|
|
16
|
+
Requires-Dist: google-api-python-client==2.2.0
|
|
17
|
+
Requires-Dist: google-auth==1.29.0
|
|
18
|
+
Requires-Dist: google-auth-httplib2==0.1.0
|
|
19
|
+
Requires-Dist: google-auth-oauthlib==0.4.6
|
|
20
|
+
Requires-Dist: google-cloud-core==1.6.0
|
|
21
|
+
Requires-Dist: google-cloud-firestore==2.1.0
|
|
22
|
+
Requires-Dist: google-cloud-storage==1.37.1
|
|
23
|
+
Requires-Dist: google-crc32c==1.1.2
|
|
24
|
+
Requires-Dist: google-pasta==0.2.0
|
|
25
|
+
Requires-Dist: google-resumable-media==1.2.0
|
|
26
|
+
Requires-Dist: googleapis-common-protos==1.53.0
|
|
27
|
+
Requires-Dist: setuptools
|
|
28
|
+
Requires-Dist: cone.app<1.1.0
|
|
29
|
+
Requires-Dist: requests
|
|
30
|
+
Requires-Dist: firebase_admin==4.5.3
|
|
31
|
+
Provides-Extra: test
|
|
32
|
+
Requires-Dist: zope.testrunner; extra == "test"
|
|
33
|
+
Dynamic: author
|
|
34
|
+
Dynamic: author-email
|
|
35
|
+
Dynamic: classifier
|
|
36
|
+
Dynamic: description
|
|
37
|
+
Dynamic: home-page
|
|
38
|
+
Dynamic: keywords
|
|
39
|
+
Dynamic: license
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
Dynamic: provides-extra
|
|
42
|
+
Dynamic: requires-dist
|
|
43
|
+
Dynamic: summary
|
|
44
|
+
|
|
45
|
+
.. image:: https://img.shields.io/pypi/v/cone.firebase.svg
|
|
46
|
+
:target: https://pypi.python.org/pypi/cone.firebase
|
|
47
|
+
:alt: Latest PyPI version
|
|
48
|
+
|
|
49
|
+
.. image:: https://img.shields.io/pypi/dm/cone.firebase.svg
|
|
50
|
+
:target: https://pypi.python.org/pypi/cone.firebase
|
|
51
|
+
:alt: Number of PyPI downloads
|
|
52
|
+
|
|
53
|
+
.. image:: https://travis-ci.org/bluedynamics/cone.firebase.svg?branch=master
|
|
54
|
+
:target: https://travis-ci.org/bluedynamics/cone.firebase
|
|
55
|
+
|
|
56
|
+
.. image:: https://coveralls.io/repos/github/bluedynamics/cone.firebase/badge.svg?branch=master
|
|
57
|
+
:target: https://coveralls.io/github/bluedynamics/cone.firebase?branch=master
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
This package provides a firebase integration in to cone.app.
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
Installation
|
|
64
|
+
------------
|
|
65
|
+
|
|
66
|
+
Include ``cone.firebase`` to install dependencies in your application's
|
|
67
|
+
``setup.py``.
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
Configuration
|
|
71
|
+
-------------
|
|
72
|
+
|
|
73
|
+
Adopt your application config ini file to define firebase related API keys.
|
|
74
|
+
|
|
75
|
+
.. code-block:: ini
|
|
76
|
+
|
|
77
|
+
[app:my_app]
|
|
78
|
+
use = egg:cone.app#main
|
|
79
|
+
|
|
80
|
+
cone.plugins =
|
|
81
|
+
cone.firebase
|
|
82
|
+
|
|
83
|
+
cone.authenticator = firebase
|
|
84
|
+
|
|
85
|
+
firebase.web_api_key = xxx
|
|
86
|
+
firebase.service_account_json_file = path/to/service_account.json
|
|
87
|
+
|
|
88
|
+
also in your ugm.xml you can optionally add the `firebase_user` checkbox:
|
|
89
|
+
|
|
90
|
+
.. code-block:: xml
|
|
91
|
+
|
|
92
|
+
<users_form_attrmap>
|
|
93
|
+
...
|
|
94
|
+
<elem>
|
|
95
|
+
<key>firebase_user</key>
|
|
96
|
+
<value>Firebase User</value>
|
|
97
|
+
</elem>
|
|
98
|
+
</users_form_attrmap>
|
|
99
|
+
|
|
100
|
+
When you want to upgrade an existing user to FB, you can do this by checking
|
|
101
|
+
`firebase_user` in the user edit form, butt attention: you have to manually
|
|
102
|
+
enter the password since the firebase API expects the password in cleartext.
|
|
103
|
+
|
|
104
|
+
Contributors
|
|
105
|
+
============
|
|
106
|
+
|
|
107
|
+
- Robert Niederreiter
|
|
108
|
+
- Phil Auersperg
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
Changes
|
|
112
|
+
=======
|
|
113
|
+
|
|
114
|
+
0.1 (2025-10-25)
|
|
115
|
+
----------------
|
|
116
|
+
|
|
117
|
+
- Initial
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
License
|
|
121
|
+
=======
|
|
122
|
+
|
|
123
|
+
Copyright (c) 2021, BlueDynamics Alliance, Austria
|
|
124
|
+
Copyright (c) 2021, Cone Contributors
|
|
125
|
+
All rights reserved.
|
|
126
|
+
|
|
127
|
+
Redistribution and use in source and binary forms, with or without
|
|
128
|
+
modification, are permitted provided that the following conditions are met:
|
|
129
|
+
|
|
130
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
131
|
+
list of conditions and the following disclaimer.
|
|
132
|
+
|
|
133
|
+
* Redistributions in binary form must reproduce the above copyright notice, this
|
|
134
|
+
list of conditions and the following disclaimer in the documentation and/or
|
|
135
|
+
other materials provided with the distribution.
|
|
136
|
+
|
|
137
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
138
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
139
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
140
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
141
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
142
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
143
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
144
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
145
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
146
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
.. image:: https://img.shields.io/pypi/v/cone.firebase.svg
|
|
2
|
+
:target: https://pypi.python.org/pypi/cone.firebase
|
|
3
|
+
:alt: Latest PyPI version
|
|
4
|
+
|
|
5
|
+
.. image:: https://img.shields.io/pypi/dm/cone.firebase.svg
|
|
6
|
+
:target: https://pypi.python.org/pypi/cone.firebase
|
|
7
|
+
:alt: Number of PyPI downloads
|
|
8
|
+
|
|
9
|
+
.. image:: https://travis-ci.org/bluedynamics/cone.firebase.svg?branch=master
|
|
10
|
+
:target: https://travis-ci.org/bluedynamics/cone.firebase
|
|
11
|
+
|
|
12
|
+
.. image:: https://coveralls.io/repos/github/bluedynamics/cone.firebase/badge.svg?branch=master
|
|
13
|
+
:target: https://coveralls.io/github/bluedynamics/cone.firebase?branch=master
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
This package provides a firebase integration in to cone.app.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
Installation
|
|
20
|
+
------------
|
|
21
|
+
|
|
22
|
+
Include ``cone.firebase`` to install dependencies in your application's
|
|
23
|
+
``setup.py``.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Configuration
|
|
27
|
+
-------------
|
|
28
|
+
|
|
29
|
+
Adopt your application config ini file to define firebase related API keys.
|
|
30
|
+
|
|
31
|
+
.. code-block:: ini
|
|
32
|
+
|
|
33
|
+
[app:my_app]
|
|
34
|
+
use = egg:cone.app#main
|
|
35
|
+
|
|
36
|
+
cone.plugins =
|
|
37
|
+
cone.firebase
|
|
38
|
+
|
|
39
|
+
cone.authenticator = firebase
|
|
40
|
+
|
|
41
|
+
firebase.web_api_key = xxx
|
|
42
|
+
firebase.service_account_json_file = path/to/service_account.json
|
|
43
|
+
|
|
44
|
+
also in your ugm.xml you can optionally add the `firebase_user` checkbox:
|
|
45
|
+
|
|
46
|
+
.. code-block:: xml
|
|
47
|
+
|
|
48
|
+
<users_form_attrmap>
|
|
49
|
+
...
|
|
50
|
+
<elem>
|
|
51
|
+
<key>firebase_user</key>
|
|
52
|
+
<value>Firebase User</value>
|
|
53
|
+
</elem>
|
|
54
|
+
</users_form_attrmap>
|
|
55
|
+
|
|
56
|
+
When you want to upgrade an existing user to FB, you can do this by checking
|
|
57
|
+
`firebase_user` in the user edit form, butt attention: you have to manually
|
|
58
|
+
enter the password since the firebase API expects the password in cleartext.
|
|
59
|
+
|
|
60
|
+
Contributors
|
|
61
|
+
============
|
|
62
|
+
|
|
63
|
+
- Robert Niederreiter
|
|
64
|
+
- Phil Auersperg
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from setuptools import find_packages
|
|
2
|
+
from setuptools import setup
|
|
3
|
+
from setuptools.command.test import test
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def read_file(name):
|
|
8
|
+
with open(os.path.join(os.path.dirname(__file__), name)) as f:
|
|
9
|
+
return f.read()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
version = '0.1'
|
|
13
|
+
shortdesc = 'Firebase integration for cone.app'
|
|
14
|
+
longdesc = '\n\n'.join([read_file(name) for name in [
|
|
15
|
+
'README.rst',
|
|
16
|
+
'CHANGES.rst',
|
|
17
|
+
'LICENSE.rst'
|
|
18
|
+
]])
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Test(test):
|
|
22
|
+
|
|
23
|
+
def run_tests(self):
|
|
24
|
+
from cone.firebase import tests
|
|
25
|
+
tests.run_tests()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
setup(
|
|
29
|
+
name='cone.firebase',
|
|
30
|
+
version=version,
|
|
31
|
+
description=shortdesc,
|
|
32
|
+
long_description=longdesc,
|
|
33
|
+
classifiers=[
|
|
34
|
+
'Environment :: Web Environment',
|
|
35
|
+
'Programming Language :: Python',
|
|
36
|
+
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
|
37
|
+
],
|
|
38
|
+
keywords='node pyramid cone web',
|
|
39
|
+
author='Cone Contributors',
|
|
40
|
+
author_email='dev@conestack.org',
|
|
41
|
+
url='http://github.com/conestack/cone.firebase',
|
|
42
|
+
license='Simplified BSD',
|
|
43
|
+
packages=find_packages('src'),
|
|
44
|
+
package_dir={'': 'src'},
|
|
45
|
+
namespace_packages=['cone'],
|
|
46
|
+
include_package_data=True,
|
|
47
|
+
zip_safe=False,
|
|
48
|
+
install_requires=[
|
|
49
|
+
'proto-plus==1.18.1',
|
|
50
|
+
'google-api-core==1.26.3',
|
|
51
|
+
'google-api-python-client==2.2.0',
|
|
52
|
+
'google-auth==1.29.0',
|
|
53
|
+
'google-auth-httplib2==0.1.0',
|
|
54
|
+
'google-auth-oauthlib==0.4.6',
|
|
55
|
+
'google-cloud-core==1.6.0',
|
|
56
|
+
'google-cloud-firestore==2.1.0',
|
|
57
|
+
'google-cloud-storage==1.37.1',
|
|
58
|
+
'google-crc32c==1.1.2',
|
|
59
|
+
'google-pasta==0.2.0',
|
|
60
|
+
'google-resumable-media==1.2.0',
|
|
61
|
+
'googleapis-common-protos==1.53.0',
|
|
62
|
+
|
|
63
|
+
'setuptools',
|
|
64
|
+
'cone.app<1.1.0',
|
|
65
|
+
'requests',
|
|
66
|
+
'firebase_admin==4.5.3',
|
|
67
|
+
# 'protobuf==3.15.8'
|
|
68
|
+
],
|
|
69
|
+
extras_require=dict(test=['zope.testrunner']),
|
|
70
|
+
tests_require=['zope.testrunner'],
|
|
71
|
+
cmdclass=dict(test=Test)
|
|
72
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__import__('pkg_resources').declare_namespace(__name__)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from cone.app import main_hook
|
|
2
|
+
from cone.app.interfaces import IAuthenticator
|
|
3
|
+
from cone.firebase.authentication import FirebaseAuthenticator
|
|
4
|
+
from cone.ugm.browser.principal import user_field
|
|
5
|
+
from yafowil.base import factory
|
|
6
|
+
import firebase_admin
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger('cone.firebase')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FirebaseConfig(object):
|
|
15
|
+
web_api_key = None
|
|
16
|
+
service_account_json = None
|
|
17
|
+
|
|
18
|
+
def __init__(self, web_api_key, service_account_json_file):
|
|
19
|
+
self.web_api_key = web_api_key
|
|
20
|
+
with open(service_account_json_file, 'r') as f:
|
|
21
|
+
self.service_account_json = json.loads(f.read())
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# FirebaseConfig singleton
|
|
25
|
+
config = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def load_firebase_config(settings):
|
|
29
|
+
global config
|
|
30
|
+
config = FirebaseConfig(
|
|
31
|
+
settings['firebase.web_api_key'],
|
|
32
|
+
settings['firebase.service_account_json_file']
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def initialize_firebase_admin():
|
|
37
|
+
service_account_json = config.service_account_json
|
|
38
|
+
cred = firebase_admin.credentials.Certificate(service_account_json)
|
|
39
|
+
if not firebase_admin._apps:
|
|
40
|
+
firebase_admin.initialize_app(cred)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@user_field('firebase_user')
|
|
44
|
+
def firebase_user_field_factory(form, label, value):
|
|
45
|
+
return factory(
|
|
46
|
+
'field:label:help:error:checkbox',
|
|
47
|
+
value=value,
|
|
48
|
+
props={
|
|
49
|
+
'label': label,
|
|
50
|
+
'datatype': bool,
|
|
51
|
+
'help': "shall this user also be managed in Firebase?",
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@user_field('email_verified')
|
|
56
|
+
def email_verified(form, label, value):
|
|
57
|
+
return factory(
|
|
58
|
+
'field:label:help:error:checkbox',
|
|
59
|
+
value=value,
|
|
60
|
+
props={
|
|
61
|
+
'label': label,
|
|
62
|
+
'datatype': bool,
|
|
63
|
+
'help': "is user verified by email?",
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@user_field('fullname')
|
|
68
|
+
def fullname_field_factory(form, label, value):
|
|
69
|
+
return factory(
|
|
70
|
+
'field:label:error:text',
|
|
71
|
+
value=value,
|
|
72
|
+
props={
|
|
73
|
+
'label': label,
|
|
74
|
+
'datatype': str,
|
|
75
|
+
'required': "fullname not given"
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@main_hook
|
|
80
|
+
def initialize_firebase(config, global_config, settings):
|
|
81
|
+
load_firebase_config(settings)
|
|
82
|
+
initialize_firebase_admin()
|
|
83
|
+
config.registry.registerUtility(
|
|
84
|
+
FirebaseAuthenticator,
|
|
85
|
+
IAuthenticator,
|
|
86
|
+
'firebase'
|
|
87
|
+
)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
from cone.firebase.management import get_device_tokens_for_user # noqa
|
|
2
|
+
from cone.firebase.management import register_device_token_for_user # noqa
|
|
3
|
+
from cone.firebase.messaging import send_message # noqa
|
|
4
|
+
from cone.firebase.messaging import send_message_to_user # noqa
|
|
5
|
+
from cone.firebase.messaging import send_messages # noqa
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from cone.app import security
|
|
2
|
+
from cone.app import ugm_backend
|
|
3
|
+
from cone.app.interfaces import IAuthenticator
|
|
4
|
+
from zope.interface import implementer
|
|
5
|
+
import cone.firebase
|
|
6
|
+
import json
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
REST_API_URL_LOGIN = f"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def sign_in_with_email_and_password(email, password, api_key, return_secure_token = True):
|
|
14
|
+
"""
|
|
15
|
+
borrowed from here:
|
|
16
|
+
https://github.com/billydh/python-firebase-admin-sdk-demo/blob/master/sign_in_with_email_and_password.py
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
payload = json.dumps({
|
|
21
|
+
"email": email or "",
|
|
22
|
+
"password": password or "",
|
|
23
|
+
"returnSecureToken": return_secure_token
|
|
24
|
+
})
|
|
25
|
+
except Exception as ex:
|
|
26
|
+
cone.firebase.logger.error(f"error encoding email: {email} and password: {password}")
|
|
27
|
+
raise
|
|
28
|
+
|
|
29
|
+
r = requests.post(REST_API_URL_LOGIN,
|
|
30
|
+
params={"key": api_key},
|
|
31
|
+
data=payload)
|
|
32
|
+
|
|
33
|
+
return r.json()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@implementer(IAuthenticator)
|
|
37
|
+
class FirebaseAuthenticator:
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def authenticate(cls, uid: str, pwd: str) -> str:
|
|
41
|
+
"""
|
|
42
|
+
does firebase authentication and in the case of success
|
|
43
|
+
checks if the uid exists in self.users, if not it creates the user there
|
|
44
|
+
when fb login is unsuccessful delegates it to
|
|
45
|
+
:return: login of logged in user
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
res = sign_in_with_email_and_password(uid, pwd, cone.firebase.config.web_api_key)
|
|
49
|
+
|
|
50
|
+
ugm = ugm_backend.ugm
|
|
51
|
+
users = ugm.users
|
|
52
|
+
|
|
53
|
+
if res.get("kind") == 'identitytoolkit#VerifyPasswordResponse':
|
|
54
|
+
id = res["localId"]
|
|
55
|
+
if id not in users:
|
|
56
|
+
users.create(
|
|
57
|
+
id,
|
|
58
|
+
login="email",
|
|
59
|
+
email=res["email"],
|
|
60
|
+
fullname=res.get("displayName"),
|
|
61
|
+
registered=res["registered"],
|
|
62
|
+
idtoken=res["idToken"]
|
|
63
|
+
)
|
|
64
|
+
# else:
|
|
65
|
+
# user email aktualisieren
|
|
66
|
+
|
|
67
|
+
user = users[id]
|
|
68
|
+
if hasattr(users, "on_authenticated"):
|
|
69
|
+
users.on_authenticated(res["localId"])
|
|
70
|
+
# user.passwd(None, pwd) # it is to decide if we need local pwds
|
|
71
|
+
return id
|
|
72
|
+
else:
|
|
73
|
+
# if not found in firebase login locally
|
|
74
|
+
auth = users.authenticate(uid, pwd)
|
|
75
|
+
if auth:
|
|
76
|
+
return uid
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
from cone.app import ugm_backend
|
|
2
|
+
from cone.ugm.events import UserCreatedEvent
|
|
3
|
+
from cone.ugm.events import UserDeletedEvent
|
|
4
|
+
from cone.ugm.events import UserModifiedEvent
|
|
5
|
+
from firebase_admin import auth
|
|
6
|
+
from firebase_admin.auth import UserNotFoundError
|
|
7
|
+
from pyramid.security import remember
|
|
8
|
+
from typing import List
|
|
9
|
+
from typing import Tuple
|
|
10
|
+
from yafowil.base import ExtractionError
|
|
11
|
+
from zope.event import classhandler
|
|
12
|
+
import cone.firebase
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
FIREBASE_DEVICE_TOKENS = "firebase_device_tokens"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@classhandler.handler(UserCreatedEvent)
|
|
19
|
+
def on_user_created(event: UserCreatedEvent):
|
|
20
|
+
user = event.principal
|
|
21
|
+
email = user.attrs["mail"]
|
|
22
|
+
uid = user.attrs["id"]
|
|
23
|
+
# if firebase_user checkbox is not installed, we want to add all users to FB
|
|
24
|
+
if "firebase_user" not in user.attrs or user.attrs["firebase_user"]:
|
|
25
|
+
if email:
|
|
26
|
+
user_record = create_firebase_user(user, event.password)
|
|
27
|
+
cone.firebase.logger.info(f"user {uid} added to firebase with email {email} -> {user_record.__dict__}")
|
|
28
|
+
else:
|
|
29
|
+
cone.firebase.logger.warn(f"user {uid} has no email -> not added to firebase")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def create_firebase_user(user, password):
|
|
33
|
+
fullname = user.attrs["fullname"]
|
|
34
|
+
user_record = auth.create_user(
|
|
35
|
+
uid=user.attrs["id"],
|
|
36
|
+
email=user.attrs["mail"],
|
|
37
|
+
# phone_number='+15555550100',
|
|
38
|
+
# email_verified=True,
|
|
39
|
+
password=password,
|
|
40
|
+
display_name=fullname,
|
|
41
|
+
# photo_url='http://www.example.com/12345678/photo.png',
|
|
42
|
+
disabled=False
|
|
43
|
+
)
|
|
44
|
+
user.attrs["login"] = "mail"
|
|
45
|
+
return user_record
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@classhandler.handler(UserModifiedEvent)
|
|
49
|
+
def on_user_modified(event: UserModifiedEvent):
|
|
50
|
+
user = event.principal
|
|
51
|
+
email = user.attrs["mail"]
|
|
52
|
+
uid = user.attrs["id"]
|
|
53
|
+
try:
|
|
54
|
+
fbuser = auth.get_user(uid)
|
|
55
|
+
except UserNotFoundError:
|
|
56
|
+
# user does not exist in firebase, lets push it to fb, the password has to be specified by hand, otherwise the
|
|
57
|
+
# hashed password will be set in fb!
|
|
58
|
+
if user.attrs.get("firebase_user", False):
|
|
59
|
+
cone.firebase.logger.warn(f"user wth id {uid} not found in firebase, creating it in fb")
|
|
60
|
+
if not user.attrs["fullname"]:
|
|
61
|
+
raise ExtractionError("Fullname not given")
|
|
62
|
+
fbuser = create_firebase_user(user, event.password)
|
|
63
|
+
cone.firebase.logger.warn(f"created user wth id {uid} in fb")
|
|
64
|
+
else:
|
|
65
|
+
fbuser = None
|
|
66
|
+
cone.firebase.logger.warn(f"user wth id {uid} not found in firebase")
|
|
67
|
+
|
|
68
|
+
if email and fbuser:
|
|
69
|
+
fullname = user.attrs["fullname"]
|
|
70
|
+
password = event.password
|
|
71
|
+
|
|
72
|
+
params = dict(
|
|
73
|
+
email=email,
|
|
74
|
+
# phone_number='+15555550100',
|
|
75
|
+
display_name=fullname,
|
|
76
|
+
disabled=False)
|
|
77
|
+
|
|
78
|
+
if event.password:
|
|
79
|
+
params["password"] = event.password
|
|
80
|
+
|
|
81
|
+
res = auth.update_user(
|
|
82
|
+
uid,
|
|
83
|
+
**params
|
|
84
|
+
)
|
|
85
|
+
cone.firebase.logger.info(f"user {uid} changes promoted to firebase with email {email} -> {res}")
|
|
86
|
+
else:
|
|
87
|
+
cone.firebase.logger.warn(f"user {uid} has no email -> not added to firebase")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@classhandler.handler(UserDeletedEvent)
|
|
91
|
+
def on_user_deleted(event):
|
|
92
|
+
user = event.principal
|
|
93
|
+
uid = user.attrs["id"]
|
|
94
|
+
try:
|
|
95
|
+
fbuser = auth.get_user(uid)
|
|
96
|
+
auth.delete_user(uid)
|
|
97
|
+
cone.firebase.logger.info(f"user with id {uid} deleted in firebase")
|
|
98
|
+
except UserNotFoundError:
|
|
99
|
+
cone.firebase.logger.warn(f"user with id {uid} not found in firebase -> user not deleted in fb")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def authenticate_with_id_token(request, id_token: str) -> Tuple[str, str]:
|
|
103
|
+
"""
|
|
104
|
+
uses the firebase ID token to login without password
|
|
105
|
+
needs installed UGM user folder located at AppRoot()["users"]
|
|
106
|
+
|
|
107
|
+
:return: tuple (user_id, auth token)
|
|
108
|
+
"""
|
|
109
|
+
# users = AppRoot()["users"]
|
|
110
|
+
cone.firebase.logger.info(f"loggin in with firebase id token: {id_token}")
|
|
111
|
+
ugm = ugm_backend.ugm
|
|
112
|
+
users = ugm.users
|
|
113
|
+
res = auth.verify_id_token(id_token)
|
|
114
|
+
user_id = res["user_id"]
|
|
115
|
+
if user_id not in users:
|
|
116
|
+
# TODO: creation shall take place in security.authenticate()
|
|
117
|
+
cone.firebase.logger.info(f"user with id {user_id} not stored locally - creating")
|
|
118
|
+
users.create(
|
|
119
|
+
user_id,
|
|
120
|
+
login="email",
|
|
121
|
+
email=res["email"],
|
|
122
|
+
fullname=res.get("name", ""),
|
|
123
|
+
email_verified=res["email_verified"],
|
|
124
|
+
phone=res.get("phone_number", ""),
|
|
125
|
+
idtoken=id_token
|
|
126
|
+
)
|
|
127
|
+
cone.firebase.logger.info(f"user with {user_id} successfully created")
|
|
128
|
+
|
|
129
|
+
return user_id, remember(request, user_id)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def register_device_token_for_user(login: str, token: str) -> List[str]:
|
|
133
|
+
"""
|
|
134
|
+
registers a device token for a given user
|
|
135
|
+
:param login: email or uid
|
|
136
|
+
:param token: firebase device token
|
|
137
|
+
"""
|
|
138
|
+
users = ugm_backend.ugm.users
|
|
139
|
+
if login not in users:
|
|
140
|
+
uid = users.id_for_login(login)
|
|
141
|
+
else:
|
|
142
|
+
uid = login
|
|
143
|
+
|
|
144
|
+
user = users[uid]
|
|
145
|
+
tokens = user.attrs.get(FIREBASE_DEVICE_TOKENS, []) or []
|
|
146
|
+
if token not in tokens:
|
|
147
|
+
user.attrs[FIREBASE_DEVICE_TOKENS] = list(tokens) + [token]
|
|
148
|
+
|
|
149
|
+
return user.attrs[FIREBASE_DEVICE_TOKENS]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def unregister_device_token_for_user(login: str, token: str):
|
|
153
|
+
"""
|
|
154
|
+
registers a device token for a given user
|
|
155
|
+
:param login: email or uid
|
|
156
|
+
:param token: firebase device token
|
|
157
|
+
"""
|
|
158
|
+
users = ugm_backend.ugm.users
|
|
159
|
+
if login not in users:
|
|
160
|
+
uid = users.id_for_login(login)
|
|
161
|
+
else:
|
|
162
|
+
uid = login
|
|
163
|
+
|
|
164
|
+
user = users[uid]
|
|
165
|
+
tokens = user.attrs.get(FIREBASE_DEVICE_TOKENS, []) or []
|
|
166
|
+
if token in tokens:
|
|
167
|
+
user.attrs[FIREBASE_DEVICE_TOKENS] = [tok for tok in tokens if token != tok]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def get_device_tokens_for_user(login: str) -> List[str]:
|
|
171
|
+
users = ugm_backend.ugm.users
|
|
172
|
+
if login not in users:
|
|
173
|
+
uid = users.id_for_login(login)
|
|
174
|
+
else:
|
|
175
|
+
uid = login
|
|
176
|
+
|
|
177
|
+
if uid in users:
|
|
178
|
+
user = users[uid]
|
|
179
|
+
return user.attrs.get(FIREBASE_DEVICE_TOKENS, []) or []
|
|
180
|
+
else:
|
|
181
|
+
return []
|