Skip to content

Commit

Permalink
Add email verification feature (#21672)
Browse files Browse the repository at this point in the history
* feat(): disable devhub.verify_email without waffle switch

* chore(devhub): migration - add suppress-email waffle switch + docs
  • Loading branch information
KevinMind committed Jan 9, 2024
1 parent 1c38392 commit 14aded3
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/topics/development/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ Development
translations
search
docs
waffle
../../../README.rst
133 changes: 133 additions & 0 deletions docs/topics/development/waffle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Waffle

We use [waffle](https://waffle.readthedocs.io/en/stable/) for managing feature access in production.

## Why switches and not flags

We prefer to use [switches](https://waffle.readthedocs.io/en/stable/types/switch.html)
over flags in most cases as switches are:

- switches are simple
- switches are easy to reason about

Flags can be used if you want to do a gradual rollout a feature over time or to a subset of users.

We have a flag `2fa-enforcement-for-developers-and-special-users` in production now.

## Creating/Deleting a switch

Switches are added via database migrations.
This ensures the switch exists in all environments once the migration is run.

To create or remove a switch,
first create an empty migration in the app where your switch will live.

```bash
python ./manage.py makemigrations <app> --empty
```

### Creating a switch

add the switch in the migration

```python
from django.db import migrations

from olympia.core.db.migrations import CreateWaffleSwitch

class Migration(migrations.Migration):

dependencies = [
('app', '0001_auto_20220531_2434'),
]

operations = [
CreateWaffleSwitch('foo')
]
```

### Deleting a switch

remove the switch in the migration

```python

from django.db import migrations

from olympia.core.db.migrations import DeleteWaffleSwitch

class Migration(migrations.Migration):

dependencies = [
('app', '0001_auto_20220531_2434'),
]

operations = [
DeleteWaffleSwitch('foo')
]
```

## Using a switch

Use your switch in python code

```python
if waffle.switch_is_active('foo'):
# do something
```

Use your switch in jinja2

```django
{% if waffle.switch_is_active('foo') %}
<p>foo is active</p>
{% endif %}
```

## Testing

Testing the result of a switch being on or off is important
to ensure your switch behaves appropriately. We can override the value of a switch easily.

Override for an entire test case

```python
# Override an entire test case class
@override_switch('foo', active=True)
class TestFoo(TestCase):
def test_bar(self):
assert waffle.switch_is_active('foo')

# Override an individual test method
@override_switch('foo', active=False)
def test_baz(self):
assert not waffle.switch_is_active('foo')
```

## Enabling your switch

Once your switch is deployed, you can enable it in a given environment by following these steps.

1. ssh into a kubernetes pod in the environment you want to enable the switch in. ([instructions][devops])
2. run the CLI command to enable your switch ([instructions][waffle-cli])

Toggling a switch on

```bash
./manage.py waffle_switch foo on
```

Once you've ensured that it works on dev, the typical way of doing things would be to add that manage.py command
to the deploy instructions for the relevant tag.
The engineer responsible for the tag would run the command on stage,
then SRE would run it in production on deploy.

## Cleanup

After a switch is enabled for all users and is no longer needed, you can remove it by:

1. Deleting all code referring to the switch.
2. adding a migration to remove the flag.

[devops]: https://mozilla-hub.atlassian.net/wiki/spaces/FDPDT/pages/98795521/DevOps#How-to-run-./manage.py-commands-in-an-environment "Devops"
[waffle-cli]: https://waffle.readthedocs.io/en/stable/usage/cli.html#switches "Waffle CLI"
15 changes: 15 additions & 0 deletions src/olympia/devhub/migrations/0006_auto_20240109_1118.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Generated by Django 4.2.8 on 2024-01-09 11:18

from django.db import migrations

from olympia.core.db.migrations import CreateWaffleSwitch

class Migration(migrations.Migration):

dependencies = [
('devhub', '0005_auto_20220531_1043'),
]

operations = [
CreateWaffleSwitch('suppressed-email')
]
11 changes: 11 additions & 0 deletions src/olympia/devhub/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2184,6 +2184,7 @@ def test_link_to_stats_for_dictionaries(self):
)


@override_switch('suppressed-email', active=True)
class TestVerifyEmail(TestCase):
def setUp(self):
super().setUp()
Expand Down Expand Up @@ -2238,6 +2239,16 @@ def _assert_redirect_self(self, response, url=None):
url = self.url if url is None else url
self.assert3xx(response, url)

@override_switch('suppressed-email', active=False)
def test_suppressed_email_waffle_disabled(self):
self._create_suppressed_email(self.user_profile)

assert self.user_profile.suppressed_email

response = self._get()

self.assert3xx(response, reverse('devhub.addons'))

def test_hide_suppressed_email_snippet(self):
"""
on verification page, do not show the suppressed email snippet
Expand Down
3 changes: 3 additions & 0 deletions src/olympia/devhub/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2114,6 +2114,9 @@ def email_verification(request):
email_verification = request.user.email_verification
suppressed_email = request.user.suppressed_email

if not waffle.switch_is_active('suppressed-email'):
return redirect('devhub.addons')

if request.method == 'POST':
if email_verification:
email_verification.delete()
Expand Down

0 comments on commit 14aded3

Please sign in to comment.