Add email verification feature (#21672)
* feat(): disable devhub.verify_email without waffle switch * chore(devhub): migration - add suppress-email waffle switch + docs
This commit is contained in:
Родитель
1c38392c3f
Коммит
14aded3570
|
@ -19,4 +19,5 @@ Development
|
|||
translations
|
||||
search
|
||||
docs
|
||||
waffle
|
||||
../../../README.rst
|
||||
|
|
|
@ -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"
|
|
@ -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')
|
||||
]
|
|
@ -2184,6 +2184,7 @@ class TestStatsLinksInManageMySubmissionsPage(TestCase):
|
|||
)
|
||||
|
||||
|
||||
@override_switch('suppressed-email', active=True)
|
||||
class TestVerifyEmail(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
@ -2238,6 +2239,16 @@ class TestVerifyEmail(TestCase):
|
|||
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
|
||||
|
|
|
@ -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()
|
||||
|
|
Загрузка…
Ссылка в новой задаче