8000 Merge branch 'master' into mfa-totp · CyberSys/firebase-admin-python@cde81fb · GitHub
[go: up one dir, main page]

Skip to content

Commit cde81fb

Browse files
authored
Merge branch 'master' into mfa-totp
2 parents c67e047 + 3c39186 commit cde81fb

File tree

15 files changed

+181
-71
lines changed

15 files changed

+181
-71
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
python: ['3.7', '3.8', '3.9', '3.10', 'pypy3.8']
1212

1313
steps:
14-
- uses: actions/checkout@v3
14+
- uses: actions/checkout@v4
1515
- name: Set up Python ${{ matrix.python }}
1616
uses: actions/setup-python@v4
1717
with:
@@ -22,10 +22,10 @@ jobs:
2222
pip install -r requirements.txt
2323
- name: Test with pytest
2424
run: pytest
25-
- name: Set up Node.js 16
26-
uses: actions/setup-node@v1
25+
- name: Set up Node.js 20
26+
uses: actions/setup-node@v4
2727
with:
28< 6D4E span class="diff-text-marker">-
node-version: 16.x
28+
node-version: 20
2929
- name: Run integration tests against emulator
3030
run: |
3131
npm install -g firebase-tools
@@ -34,7 +34,7 @@ jobs:
3434
lint:
3535
runs-on: ubuntu-latest
3636
steps:
37-
- uses: actions/checkout@v3
37+
- uses: actions/checkout@v4
3838
- name: Set up Python 3.7
3939
uses: actions/setup-python@v4
4040
with:

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ jobs:
8787
# 3. with the label 'release:publish', and
8888
# 4. the title prefix '[chore] Release '.
8989
if: github.event.pull_request.merged &&
90-
github.ref == 'master' &&
90+
github.ref == 'refs/heads/master' &&
9191
contains(github.event.pull_request.labels.*.name, 'release:publish') &&
9292
startsWith(github.event.pull_request.title, '[chore] Release ')
9393

CONTRIBUTING.md

Lines changed: 89 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -162,44 +162,95 @@ pytest --cov=firebase_admin --cov=tests
162162

163163
### Integration Testing
164164

165-
A suite of integration tests are available under the `integration/` directory.
166-
These tests are designed to run against an actual Firebase project. Create a new
167-
project in the [Firebase Console](https://console.firebase.google.com), if you
168-
do not already have one suitable for running the tests aginst. Then obtain the
169-
following credentials from the project:
170-
171-
1. *Service account certificate*: This can be downloaded as a JSON file from
172-
the "Settings > Service Accounts" tab of the Firebase console. Copy the
173-
file into the repo so it's available at `cert.json`.
174-
2. *Web API key*: This is displayed in the "Settings > General" tab of the
175-
console. Copy it and save to a new text file at `apikey.txt`.
176-
177-
Then set up your Firebase/GCP project as follows:
178-
179-
1. Enable Firestore: Go to the Firebase Console, and select "Database" from
180-
the "Develop" menu. Click on the "Create database" button. You may choose
181-
to set up Firestore either in the locked mode or in the test mode.
182-
2. Enable password auth: Select "Authentication" from the "Develop" menu in
183-
Firebase Console. Select the "Sign-in method" tab, and enable the
184-
"Email/Password" sign-in method, including the Email link (passwordless
185-
sign-in) option.
186-
3. Enable the Firebase ML API: Go to the
187-
[Google Developers Console](
188-
https://console.developers.google.com/apis/api/firebaseml.googleapis.com/overview)
189-
and make sure your project is selected. If the API is not already enabled, click Enable.
190-
4. Enable the IAM API: Go to the
191-
[Google Cloud Platform Console](https://console.cloud.google.com) and make
192-
sure your Firebase/GCP project is selected. Select "APIs & Services >
193-
Dashboard" from the main menu, and click the "ENABLE APIS AND SERVICES"
194-
button. Search for and enable the "Identity and Access Management (IAM)
195-
API".
196-
5. Grant your service account the 'Firebase Authentication Admin' role. This is
197-
required to ensure that exported user records contain the password hashes of
198-
the user accounts:
199-
1. Go to [Google Cloud Platform Console / IAM & admin](https://console.cloud.google.com/iam-admin).
200-
2. Find your service account in the list, and click the 'pencil' icon to edit it's permissions.
201-
3. Click 'ADD ANOTHER ROLE' and choose 'Firebase Authentication Admin'.
202-
4. Click 'SAVE'.
165+
166+
Integration tests are executed against a real life Firebase project. If you do not already
167+
have one suitable for running the tests against, you can create a new project in the
168+
[Firebase Console](https://console.firebase.google.com) following the setup guide below.
169+
If you already have a Firebase project, you'll need to obtain credentials to communicate and
170+
authorize access to your Firebase project:
171+
172+
173+
1. Service account certificate: This allows access to your Firebase project through a service account
174+
which is required for all integration tests. This can be downloaded as a JSON file from the
175+
**Settings > Service Accounts** tab of the Firebase console when you click the
176+
**Generate new private key** button. Copy the file into the repo so it's available at `cert.json`.
177+
> **Note:** Service accounts should be carefully managed and their keys should never be stored in publicly accessible source code or repositories.
178+
179+
180+
2. Web API key: This allows for Auth sign-in needed for some Authentication and Tenant Management
181+
integration tests. This is displayed in the **Settings > General** tab of the Firebase console
182+
after enabling Authentication as described in the steps below. Copy it and save to a new text
183+
file at `apikey.txt`.
184+
185+
186+
Set up your Firebase project as follows:
187+
188+
189+
1. Enable Authentication:
190+
1. Go to the Firebase Console, and select **Authentication** from the **Build** menu.
191+
2. Click on **Get Started**.
192+
3. Select **Sign-in method > Add new provider > Email/Password** then enable both the
193+
**Email/Password** and **Email link (passwordless sign-in)** options.
194+
195+
196+
2. Enable Firestore:
197+
1. Go to the Firebase Console, and select **Firestore Database** from the **Build** menu.
198+
2. Click on the **Create database** button. You can choose to set up Firestore either in
199+
the production mode or in the test mode.
200+
201+
202+
3. Enable Realtime Database:
203+
1. Go to the Firebase Console, and select **Realtime Database** from the **Build** menu.
204+
2. Click on the **Create Database** button. You can choose to set up the Realtime Database
205+
either in the locked mode or in the test mode.
206+
207+
> **Note:** Integration tests are not run against the default Realtime Database reference and are
208+
instead run against a database created at `https://{PROJECT_ID}.firebaseio.com`.
209+
This second Realtime Database reference is created in the following steps.
210+
211+
3. In the **Data** tab click on the kebab menu (3 dots) and select **Create Database**.
212+
4. Enter your Project ID (Found in the **General** tab in **Account Settings**) as the
213+
**Realtime Database reference**. Again, you can choose to set up the Realtime Database
214+
either in the locked mode or in the test mode.
215+
216+
217+
4. Enable Storage:
218+
1. Go to the Firebase Console, and select **Storage** from the **Build** menu.
219+
2. Click on the **Get started** button. You can choose to set up Cloud Storage
220+
either in the production mode or in the test mode.
221+
222+
223+
5. Enable the Firebase ML API:
224+
1. Go to the
225+
[Google Cloud console | Firebase ML API](https://console.cloud.google.com/apis/api/firebaseml.googleapis.com/overview)
226+
and make sure your project is selected.
227+
2. If the API is not already enabled, click **Enable**.
228+
229+
230+
6. Enable the IAM API:
231+
1. Go to the [Google Cloud console](https://console.cloud.google.com)
232+
and make sure your Firebase project is selected.
233+
2. Select **APIs & Services** from the main menu, and click the
234+
**ENABLE APIS AND SERVICES** button.
235+
3. Search for and enable **Identity and Access Management (IAM) API** by Google Enterprise API.
236+
237+
238+
7. Enable Tenant Management:
239+
1. Go to
240+
[Google Cloud console | Identity Platform](https://console.cloud.google.com/customer-identity/)
241+
and if it is not already enabled, click **Enable**.
242+
2. Then
243+
[enable multi-tenancy](https://cloud.google.com/identity-platform/docs/multi-tenancy-quickstart#enabling_multi-tenancy)
244+
for your project.
245+
246+
247+
8. Ensure your service account has the **Firebase Authentication Admin** role. This is required
248+
to ensure that exported user records contain the password hashes of the user accounts:
249+
1. Go to [Google Cloud console | IAM & admin](https://console.cloud.google.com/iam-admin).
250+
2. Find your service account in the list. If not added click the pencil icon to edit its
251+
permissions.
252+
3. Click **ADD ANOTHER ROLE** and choose **Firebase Authentication Admin**.
253+
4. Click **SAVE**.
203254

204255

205256
Now you can invoke the integration test suite as follows:

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ requests, code review feedback, and also pull requests.
4343

4444
## Supported Python Versions
4545

46-
We currently support Python 3.7+. Firebase
46+
We currently support Python 3.7+. However, Python 3.7 support is deprecated,
47+
and developers are strongly advised to use Python 3.8 or higher. Firebase
4748
Admin Python SDK is also tested on PyPy and
4849
[Google App Engine](https://cloud.google.com/appengine/) environments.
4950

firebase_admin/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
"""About information (version, etc) for Firebase Admin SDK."""
1616

17-
__version__ = '6.2.0'
17+
__version__ = '6.3.0'
1818
__title__ = 'firebase_admin'
1919
__author__ = 'Firebase'
2020
__license__ = 'Apache License 2.0'

firebase_admin/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME):
5050
Google Application Default Credentials are used.
5151
options: A dictionary of configuration options (optional). Supported options include
5252
``databaseURL``, ``storageBucket``, ``projectId``, ``databaseAuthVariableOverride``,
53-
``serviceAccountId`` and ``httpTimeout``. If ``httpTimeout`` is not set, the SDK
54-
uses a default timeout of 120 seconds.
53+
``serviceAccountId`` and ``httpTimeout``. If ``httpTimeout`` is not set, the SDK uses
54+
a default timeout of 120 seconds.
55+
5556
name: Name of the app (optional).
5657
Returns:
5758
App: A newly initialized instance of App.

firebase_admin/_auth_client.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def create_custom_token(self, uid, developer_claims=None):
9292
return self._token_generator.create_custom_token(
9393
uid, developer_claims, tenant_id=self.tenant_id)
9494

95-
def verify_id_token(self, id_token, check_revoked=False):
95+
def verify_id_token(self, id_token, check_revoked=False, clock_skew_seconds=0):
9696
"""Verifies the signature and data for the provided JWT.
9797
9898
Accepts a signed token string, verifies that it is current, was issued
@@ -102,6 +102,8 @@ def verify_id_token(self, id_token, check_revoked=False):
102102
id_token: A string of the encoded JWT.
103103
check_revoked: Boolean, If true, checks whether the token has been revoked or
104104
the user disabled (optional).
105+
clock_skew_seconds: The number of seconds to tolerate when checking the token.
106+
Must be between 0-60. Defaults to 0.
105107
106108
Returns:
107109
dict: A dictionary of key-value pairs parsed from the decoded JWT.
@@ -124,7 +126,7 @@ def verify_id_token(self, id_token, check_revoked=False):
124126
raise ValueError('Illegal check_revoked argument. Argument must be of type '
125127
' bool, but given "{0}".'.format(type(check_revoked)))
126128

127-
verified_claims = self._token_verifier.verify_id_token(id_token)
129+
verified_claims = self._token_verifier.verify_id_token(id_token, clock_skew_seconds)
128130
if self.tenant_id:
129131
token_tenant_id = verified_claims.get('firebase', {}).get('tenant')
130132
if self.tenant_id != token_tenant_id:

firebase_admin/_token_gen.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -289,11 +289,11 @@ def __init__(self, app):
289289
invalid_token_error=InvalidSessionCookieError,
290290
expired_token_error=ExpiredSessionCookieError)
291291

292-
def verify_id_token(self, id_token):
293-
return self.id_token_verifier.verify(id_token, self.request)
292+
def verify_id_token(self, id_token, clock_skew_seconds=0):
293+
return self.id_token_verifier.verify(id_token, self.request, clock_skew_seconds)
294294

295-
def verify_session_cookie(self, cookie):
296-
return self.cookie_verifier.verify(cookie, self.request)
295+
def verify_session_cookie(self, cookie, clock_skew_seconds=0):
296+
return self.cookie_verifier.verify(cookie, self.request, clock_skew_seconds)
297297

298298

299299
class _JWTVerifier:
@@ -313,7 +313,7 @@ def __init__(self, **kwargs):
313313
self._invalid_token_error = kwargs.pop('invalid_token_error')
314314
self._expired_token_error = kwargs.pop('expired_token_error')
315315

316-
def verify(self, token, request):
316+
def verify(self, token, request, clock_skew_seconds=0):
317317
"""Verifies the signature and data for the provided JWT."""
318318
token = token.encode('utf-8') if isinstance(token, str) else token
319319
if not isinstance(token, bytes) or not token:
@@ -328,6 +328,11 @@ def verify(self, token, request):
328328
'or set your Firebase project ID as an app option. Alternatively set the '
329329
'GOOGLE_CLOUD_PROJECT environment variable.'.format(self.operation))
330330

331+
if clock_skew_seconds < 0 or clock_skew_seconds > 60:
332+
raise ValueError(
333+
'Illegal clock_skew_seconds value: {0}. Must be between 0 and 60, inclusive.'
334+
.format(clock_skew_seconds))
335+
331336
header, payload = self._decode_unverified(token)
332337
issuer = payload.get('iss')
333338
audience = payload.get('aud')
@@ -393,7 +398,8 @@ def verify(self, token, request):
393398
token,
394399
request=request,
395400
audience=self.project_id,
396-
certs_url=self.cert_url)
401+
certs_url=self.cert_url,
402+
clock_skew_in_seconds=clock_skew_seconds)
397403
verified_claims['uid'] = verified_claims['sub']
398404
return verified_claims
399405
except google.auth.exceptions.TransportError as error:

firebase_admin/_user_mgt.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ def encode_action_code_settings(settings):
540540
if not isinstance(settings.ios_bundle_id, str):
541541
raise ValueError('Invalid value provided for ios_bundle_id: {0}'
542542
.format(settings.ios_bundle_id))
543-
parameters['iosBundleId'] = settings.ios_bundle_id
543+
parameters['iOSBundleId'] = settings.ios_bundle_id
544544

545545
# android_* attributes
546546
if (settings.android_minimum_version or settings.android_install_app) \

firebase_admin/app_check.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from typing import Any, Dict
1818
import jwt
19-
from jwt import PyJWKClient, ExpiredSignatureError, InvalidTokenError
19+
from jwt import PyJWKClient, ExpiredSignatureError, InvalidTokenError, DecodeError
2020
from jwt import InvalidAudienceError, InvalidIssuerError, InvalidSignatureError
2121
from firebase_admin import _utils
2222

@@ -38,6 +38,7 @@ def verify_token(token: str, app=None) -> Dict[str, Any]:
3838
Raises:
3939
ValueError: If the app's ``project_id`` is invalid or unspecified,
4040
or if the token's headers or payload are invalid.
41+
PyJWKClientError: If PyJWKClient fails to fetch a valid signing key.
4142
"""
4243
return _get_app_check_service(app).verify_token(token)
4344

@@ -71,9 +72,14 @@ def verify_token(self, token: str) -> Dict[str, Any]:
7172
# Obtain the Firebase App Check Public Keys
7273
# Note: It is not recommended to hard code these keys as they rotate,
7374
# but you should cache them for up to 6 hours.
74-
signing_key = self._jwks_client.get_signing_key_from_jwt(token)
75-
self._has_valid_token_headers(jwt.get_unverified_header(token))
76-
verified_claims = self._decode_and_verify(token, signing_key.key)
75+
try:
76+
signing_key = self._jwks_client.get_signing_key_from_jwt(token)
77+
self._has_valid_token_headers(jwt.get_unverified_header(token))
78+
verified_claims = self._decode_and_verify(token, signing_key.key)
79+
except (InvalidTokenError, DecodeError) as exception:
80+
raise ValueError(
81+
f'Verifying App Check token failed. Error: {exception}'
82+
)
7783

7884
verified_claims['app_id'] = verified_claims.get('sub')
7985
return verified_claims

firebase_admin/auth.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ def create_custom_token(uid, developer_claims=None, app=None):
191191
return client.create_custom_token(uid, developer_claims)
192192

193193

194-
def verify_id_token(id_token, app=None, check_revoked=False):
194+
def verify_id_token(id_token, app=None, check_revoked=False, clock_skew_seconds=0):
195195
"""Verifies the signature and data for the provided JWT.
196196
197197
Accepts a signed token string, verifies that it is current, and issued
@@ -202,7 +202,8 @@ def verify_id_token(id_token, app=None, check_revoked=False):
202202
app: An App instance (optional).
203203
check_revoked: Boolean, If true, checks whether the token has been revoked or
204204
the user disabled (optional).
205-
205+
clock_skew_seconds: The number of seconds to tolerate when checking the token.
206+
Must be between 0-60. Defaults to 0.
206207
Returns:
207208
dict: A dictionary of key-value pairs parsed from the decoded JWT.
208209
@@ -217,7 +218,8 @@ def verify_id_token(id_token, app=None, check_revoked=False):
217218
record is disabled.
218219
"""
219220
client = _get_client(app)
220-
return client.verify_id_token(id_token, check_revoked=check_revoked)
221+
return client.verify_id_token(
222+
id_token, check_revoked=check_revoked, clock_skew_seconds=clock_skew_seconds)
221223

222224

223225
def create_session_cookie(id_token, expires_in, app=None):
@@ -243,7 +245,7 @@ def create_session_cookie(id_token, expires_in, app=None):
243245
return client._token_generator.create_session_cookie(id_token, expires_in)
244246

245247

246-
def verify_session_cookie(session_cookie, check_revoked=False, app=None):
248+
def verify_session_cookie(session_cookie, check_revoked=False, app=None, clock_skew_seconds=0):
247249
"""Verifies a Firebase session cookie.
248250
249251
Accepts a session cookie string, verifies that it is current, and issued
@@ -254,6 +256,7 @@ def verify_session_cookie(session_cookie, check_revoked=False, app=None):
254256
check_revoked: Boolean, if true, checks whether the cookie has been revoked or the
255257
user disabled (optional).
256258
app: An App instance (optional).
259+
clock_skew_seconds: The number of seconds to tolerate when checking the cookie.
257260
258261
Returns:
259262
dict: A dictionary of key-value pairs parsed from the decoded JWT.
@@ -270,7 +273,8 @@ def verify_session_cookie(session_cookie, check_revoked=False, app=None):
270273
"""
271274
client = _get_client(app)
272275
# pylint: disable=protected-access
273-
verified_claims = client._token_verifier.verify_session_cookie(session_cookie)
276+
verified_claims = client._token_verifier.verify_session_cookie(
277+
session_cookie, clock_skew_seconds)
274278
if check_revoked:
275279
client._check_jwt_revoked_or_disabled(
276280
verified_claims, RevokedSessionCookieError, 'session cookie')

firebase_admin/firestore.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
_FIRESTORE_ATTRIBUTE = '_firestore'
3535

3636

37-
def client(app=None):
37+
def client(app=None) -> firestore.Client:
3838
"""Returns a client that can be used to interact with Google Cloud Firestore.
3939
4040
Args:

integration/test_auth.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,7 @@ def test_verify_session_cookie_revoked(new_user, api_key):
617617
claims = auth.verify_session_cookie(session_cookie, check_revoked=True)
618618
assert claims['iat'] * 1000 >= user.tokens_valid_after_timestamp
619619

620+
620621
def test_verify_session_cookie_disabled(new_user, api_key):
621622
custom_token = auth.create_custom_token(new_user.uid)
622623
id_token = _sign_in(custom_token, api_key)

0 commit comments

Comments
 (0)
0