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.
@@ -0,0 +1,7 @@
1
+ Changes
2
+ =======
3
+
4
+ 0.1 (2025-10-25)
5
+ ----------------
6
+
7
+ - Initial
@@ -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,3 @@
1
+ include *.rst
2
+ recursive-include src *
3
+ recursive-exclude src *.pyc *.pyo
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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 []