-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
minimum_valid_iat_time
to subscriber table, and hook up the res…
…t of the fxa webhook events 🛢 (#222) * URI encode the email address for fxa login (d'oh.) * Add `minimum_valid_iat_time` to subscriber so we can easily block old access tokens. * Don't set a minimum_valid_iat_time if the user is logging out normally. * Fix unit test from failing due to missing l10n context. * Add some initial fxa webhook tests, and fix a small issue with subscriber factory not setting email correctly. * Hook up delete account fxa webhook event and add a test for it. * Call get_profile on the fxa update-user webhook as noted in the docs. * Wrap minimum_valid_iat_time with our standard db encryption
- Loading branch information
1 parent
a2133d3
commit fea6eec
Showing
12 changed files
with
330 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
...ations/versions/2024_01_09_1652-ad7cc2de5ff8_add_minimum_valid_iat_time_to_subscribers.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
"""add minimum_valid_iat_time to subscribers | ||
Revision ID: ad7cc2de5ff8 | ||
Revises: 0dc429ca07f5 | ||
Create Date: 2024-01-09 16:52:20.941572 | ||
""" | ||
import os | ||
from alembic import op | ||
import sqlalchemy as sa | ||
from sqlalchemy_utils import StringEncryptedType | ||
from sqlalchemy_utils.types.encrypted.encrypted_type import AesEngine | ||
|
||
|
||
def secret(): | ||
return os.getenv("DB_SECRET") | ||
|
||
|
||
# revision identifiers, used by Alembic. | ||
revision = 'ad7cc2de5ff8' | ||
down_revision = '0dc429ca07f5' | ||
branch_labels = None | ||
depends_on = None | ||
|
||
|
||
def upgrade() -> None: | ||
op.add_column('subscribers', sa.Column('minimum_valid_iat_time', StringEncryptedType(sa.DateTime, secret, AesEngine, "pkcs5", length=255))) | ||
|
||
|
||
def downgrade() -> None: | ||
op.drop_column('subscribers', 'minimum_valid_iat_time') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
import datetime | ||
|
||
from freezegun import freeze_time | ||
from appointment.database import models, repo | ||
|
||
from appointment.dependencies.fxa import get_webhook_auth | ||
from defines import FXA_CLIENT_PATCH | ||
|
||
|
||
class TestFXAWebhooks: | ||
def test_fxa_process_change_password(self, with_db, with_client, make_pro_subscriber, make_external_connections): | ||
"""Ensure the change password event is handled correctly""" | ||
FXA_USER_ID = 'abc-123' | ||
|
||
def override_get_webhook_auth(): | ||
return { | ||
"iss": "https://accounts.firefox.com/", | ||
"sub": FXA_USER_ID, | ||
"aud": "REMOTE_SYSTEM", | ||
"iat": 1565720808, | ||
"jti": "e19ed6c5-4816-4171-aa43-56ffe80dbda1", | ||
"events": { | ||
"https://schemas.accounts.firefox.com/event/password-change": { | ||
"changeTime": 1565721242227 | ||
} | ||
} | ||
} | ||
|
||
# Override get_webhook_auth so we don't have to mock up a valid jwt token | ||
with_client.app.dependency_overrides[get_webhook_auth] = override_get_webhook_auth | ||
|
||
# make a guy | ||
subscriber = make_pro_subscriber() | ||
subscriber_id = subscriber.id | ||
make_external_connections(subscriber_id, type=models.ExternalConnectionType.fxa, type_id=FXA_USER_ID) | ||
|
||
assert subscriber.minimum_valid_iat_time is None | ||
|
||
# Freeze time to before the changeTime timestamp and test the password change works correctly | ||
with freeze_time('Aug 13th 2019'): | ||
# Update the external connection time to match our freeze_time | ||
with with_db() as db: | ||
fxa_connection = repo.get_external_connections_by_type(db, subscriber_id, models.ExternalConnectionType.fxa, FXA_USER_ID)[0] | ||
fxa_connection.time_updated = datetime.datetime.now() | ||
db.add(fxa_connection) | ||
db.commit() | ||
|
||
response = with_client.post( | ||
"/webhooks/fxa-process", | ||
) | ||
assert response.status_code == 200, response.text | ||
|
||
with with_db() as db: | ||
subscriber = repo.get_subscriber(db, subscriber_id) | ||
assert subscriber.minimum_valid_iat_time is not None | ||
|
||
# Update the external connection time to match our current time | ||
# This will make the change password event out of date | ||
with with_db() as db: | ||
fxa_connection = repo.get_external_connections_by_type(db, subscriber_id, models.ExternalConnectionType.fxa, FXA_USER_ID)[0] | ||
fxa_connection.time_updated = datetime.datetime.now() | ||
db.add(fxa_connection) | ||
db.commit() | ||
|
||
# Reset our minimum_valid_iat_time, so we can ensure it stays None | ||
subscriber = repo.get_subscriber(db, subscriber_id) | ||
subscriber.minimum_valid_iat_time = None | ||
db.add(subscriber) | ||
db.commit() | ||
|
||
# Finally test that minimum_valid_iat_time stays the same due to an outdated password change event | ||
response = with_client.post( | ||
"/webhooks/fxa-process", | ||
) | ||
assert response.status_code == 200, response.text | ||
with with_db() as db: | ||
subscriber = repo.get_subscriber(db, subscriber_id) | ||
assert subscriber.minimum_valid_iat_time is None | ||
|
||
def test_fxa_process_change_primary_email(self, with_db, with_client, make_pro_subscriber, make_external_connections): | ||
"""Ensure the change primary email event is handled correctly""" | ||
|
||
FXA_USER_ID = 'abc-456' | ||
OLD_EMAIL = '[email protected]' | ||
NEW_EMAIL = '[email protected]' | ||
|
||
def override_get_webhook_auth(): | ||
return { | ||
"iss": "https://accounts.firefox.com/", | ||
"sub": FXA_USER_ID, | ||
"aud": "REMOTE_SYSTEM", | ||
"iat": 1565720808, | ||
"jti": "e19ed6c5-4816-4171-aa43-56ffe80dbda1", | ||
"events": { | ||
"https://schemas.accounts.firefox.com/event/profile-change": { | ||
"email": NEW_EMAIL | ||
} | ||
} | ||
} | ||
|
||
# Override get_webhook_auth so we don't have to mock up a valid jwt token | ||
with_client.app.dependency_overrides[get_webhook_auth] = override_get_webhook_auth | ||
|
||
# Make a guy with a middleschool-era email they would like to change | ||
subscriber = make_pro_subscriber(email=OLD_EMAIL) | ||
subscriber_id = subscriber.id | ||
make_external_connections(subscriber_id, type=models.ExternalConnectionType.fxa, type_id=FXA_USER_ID) | ||
|
||
assert subscriber.minimum_valid_iat_time is None | ||
assert subscriber.email == OLD_EMAIL | ||
assert subscriber.avatar_url != FXA_CLIENT_PATCH.get('subscriber_avatar_url') | ||
assert subscriber.name != FXA_CLIENT_PATCH.get('subscriber_display_name') | ||
|
||
response = with_client.post( | ||
"/webhooks/fxa-process", | ||
) | ||
assert response.status_code == 200, response.text | ||
|
||
# Refresh the subscriber and test minimum_valid_iat_time (they should be logged out), and email address | ||
with with_db() as db: | ||
subscriber = repo.get_subscriber(db, subscriber_id) | ||
assert subscriber.email == NEW_EMAIL | ||
assert subscriber.minimum_valid_iat_time is not None | ||
|
||
# Ensure our profile update occured | ||
assert subscriber.avatar_url == FXA_CLIENT_PATCH.get('subscriber_avatar_url') | ||
assert subscriber.name == FXA_CLIENT_PATCH.get('subscriber_display_name') | ||
|
||
def test_fxa_process_delete_user(self, with_db, with_client, make_pro_subscriber, make_external_connections, make_appointment, make_caldav_calendar): | ||
"""Ensure the delete user event is handled correctly""" | ||
FXA_USER_ID = 'abc-789' | ||
|
||
def override_get_webhook_auth(): | ||
return { | ||
"iss": "https://accounts.firefox.com/", | ||
"sub": FXA_USER_ID, | ||
"aud": "REMOTE_SYSTEM", | ||
"iat": 1565720810, | ||
"jti": "1b3d623a-300a-4ab8-9241-855c35586809", | ||
"events": { | ||
"https://schemas.accounts.firefox.com/event/delete-user": {} | ||
} | ||
} | ||
|
||
# Override get_webhook_auth so we don't have to mock up a valid jwt token | ||
with_client.app.dependency_overrides[get_webhook_auth] = override_get_webhook_auth | ||
|
||
subscriber = make_pro_subscriber() | ||
make_external_connections(subscriber.id, type=models.ExternalConnectionType.fxa, type_id=FXA_USER_ID) | ||
calendar = make_caldav_calendar(subscriber.id) | ||
appointment = make_appointment(calendar_id=calendar.id) | ||
|
||
response = with_client.post( | ||
"/webhooks/fxa-process", | ||
) | ||
assert response.status_code == 200, response.text | ||
|
||
with with_db() as db: | ||
# Make sure everything we created is gone. A more exhaustive check is done in the delete account test | ||
assert repo.get_subscriber(db, subscriber.id) is None | ||
assert repo.get_calendar(db, calendar.id) is None | ||
assert repo.get_appointment(db, appointment.id) is None |
Oops, something went wrong.