-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
53 changed files
with
1,625 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
name: Check code | ||
|
||
env: | ||
DOCKER_BUILDKIT: 1 | ||
|
||
on: | ||
push: | ||
branches: | ||
- 'main' | ||
- 'feature/**' | ||
- 'bugfix/**' | ||
- 'hotfix/**' | ||
- 'develop' | ||
|
||
jobs: | ||
check_web: | ||
name: Check Python | ||
|
||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- name: Set up Python 3.9 | ||
uses: actions/setup-python@v3 | ||
with: | ||
python-version: 3.9 | ||
|
||
- name: Install dependencies | ||
run: | | ||
python3 -m pip install --upgrade pip setuptools | ||
pip install -r requirements-dev.lock | ||
- name: Run Python code checks | ||
run: | | ||
make check-python-code | ||
check_migrations: | ||
name: Check migrations | ||
|
||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- run: | | ||
docker-compose build web | ||
docker-compose run web python manage.py makemigrations --check | ||
run_tests: | ||
name: Run tests | ||
|
||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- name: Run tests | ||
run: | | ||
make test |
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,3 @@ | ||
*.pyc | ||
/staticfiles/ | ||
db.sqlite3 |
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,38 @@ | ||
include envs/web | ||
|
||
define _update_requirements | ||
docker-compose run requirements bash -c "pip install -U pip setuptools && pip install -U -r /app/$(1).txt && pip freeze > /app/$(1).lock" | ||
endef | ||
|
||
.PHONY: update-requirements | ||
update-requirements: | ||
$(call _update_requirements,requirements) | ||
$(call _update_requirements,requirements-dev) | ||
|
||
.PHONY: reset-db | ||
reset-db: | ||
docker-compose up --detach ${POSTGRES_HOST} | ||
docker-compose run ${POSTGRES_HOST} dropdb -U ${POSTGRES_USER} -h ${POSTGRES_HOST} ${POSTGRES_DB} | ||
docker-compose run ${POSTGRES_HOST} createdb -U ${POSTGRES_USER} -h ${POSTGRES_HOST} ${POSTGRES_DB} | ||
docker-compose kill | ||
|
||
# -------------------------------------- Code Style ------------------------------------- | ||
|
||
.PHONY: check-python-code | ||
check-python-code: | ||
isort --check . | ||
black --check . | ||
flake8 | ||
bandit -ll -r consultation_analyser | ||
|
||
.PHONY: check-migrations | ||
check-migrations: | ||
docker-compose build web | ||
docker-compose run web python manage.py migrate | ||
docker-compose run web python manage.py makemigrations --check | ||
|
||
.PHONY: test | ||
test: | ||
docker-compose down | ||
docker-compose build tests-consultation_analyser consultation_analyser-test-db && docker-compose run --rm tests-consultation_analyser | ||
docker-compose down |
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 @@ | ||
web: python manage.py migrate && waitress-serve --port=$PORT consultation_analyser.wsgi:application |
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 @@ | ||
## Using Docker | ||
|
||
1. [Install Docker](https://docs.docker.com/get-docker/) on your machine | ||
2. `docker-compose up --build --force-recreate web` | ||
3. It's now available at: http://localhost:8000/ | ||
|
||
Migrations are run automatically at startup, and suppliers are added automatically at startup | ||
|
||
|
||
## Running tests | ||
|
||
make test | ||
|
||
|
||
## Checking code | ||
|
||
make check-python-code | ||
|
||
|
||
## How to access the admin | ||
|
||
Access to the admin is authenticated via username & password and TOTP and authorized for `staff` users. | ||
|
||
If you are the first person to get access in any given environment, e.g. your own local env or | ||
a new AWS test env then | ||
|
||
1. make yourself a superuser `docker compose run web python manage.py assign_superuser_status --email [email protected] --pwd y0urP4ssw0rd`, you dont need to set the password if you already have one. | ||
2. copy the link generated by the step above and open it on your phone to create a new TOTP account | ||
3. log in to the admin localhost/admin with your email, password and TOTP code generated on your phone/other device. | ||
|
||
Otherwise speak to an existing admin to edit your account. |
Empty file.
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,16 @@ | ||
""" | ||
ASGI config for people_survey project. | ||
It exposes the ASGI callable as a module-level variable named ``application``. | ||
For more information on this file, see | ||
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ | ||
""" | ||
|
||
import os | ||
|
||
from django.core.asgi import get_asgi_application | ||
|
||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "consultation_analyser.settings") | ||
|
||
application = get_asgi_application() |
Empty file.
14 changes: 14 additions & 0 deletions
14
consultation-analyser/consultation_analyser/consultations/admin.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,14 @@ | ||
from django.contrib import admin | ||
from django_otp.admin import OTPAdminSite | ||
|
||
from . import models | ||
|
||
|
||
class OTPAdmin(OTPAdminSite): | ||
pass | ||
|
||
|
||
admin_site = OTPAdmin(name="OTPAdmin") | ||
|
||
|
||
admin.site.register(models.User) |
6 changes: 6 additions & 0 deletions
6
consultation-analyser/consultation_analyser/consultations/apps.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,6 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class ConsultationsConfig(AppConfig): # TODO: It's likely you'll have to fix this class name | ||
default_auto_field = "django.db.models.BigAutoField" | ||
name = "consultation_analyser.consultations" |
8 changes: 8 additions & 0 deletions
8
consultation-analyser/consultation_analyser/consultations/constants.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,8 @@ | ||
BUSINESS_SPECIFIC_WORDS = [ | ||
"one big thing", | ||
"whitehall", | ||
"civil service", | ||
"home office", | ||
"cabinet office", | ||
"downing street", | ||
] |
117 changes: 117 additions & 0 deletions
117
consultation-analyser/consultation_analyser/consultations/email_handler.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,117 @@ | ||
import furl | ||
from django.conf import settings | ||
from django.contrib.auth.tokens import PasswordResetTokenGenerator | ||
from django.core.mail import send_mail | ||
from django.template.loader import render_to_string | ||
from django.urls import reverse | ||
from django.utils import timezone | ||
|
||
from consultation_analyser.consultations import models | ||
|
||
|
||
def _strip_microseconds(dt): | ||
if not dt: | ||
return None | ||
return dt.replace(microsecond=0, tzinfo=None) | ||
|
||
|
||
class EmailVerifyTokenGenerator(PasswordResetTokenGenerator): | ||
def _make_hash_value(self, user, timestamp): | ||
login_timestamp = _strip_microseconds(user.last_login) | ||
token_timestamp = _strip_microseconds(user.last_token_sent_at) | ||
return f"{user.id}{timestamp}{login_timestamp}{user.email}{token_timestamp}" | ||
|
||
|
||
EMAIL_VERIFY_TOKEN_GENERATOR = EmailVerifyTokenGenerator() | ||
PASSWORD_RESET_TOKEN_GENERATOR = PasswordResetTokenGenerator() | ||
|
||
|
||
EMAIL_MAPPING = { | ||
"email-verification": { | ||
"subject": "Confirm your email address", | ||
"template_name": "email/verification.txt", | ||
"url_name": "verify-email", | ||
"token_generator": EMAIL_VERIFY_TOKEN_GENERATOR, | ||
}, | ||
"email-register": { | ||
"subject": "Confirm your email address", | ||
"template_name": "email/verification.txt", | ||
"url_name": "verify-email-register", | ||
"token_generator": EMAIL_VERIFY_TOKEN_GENERATOR, | ||
}, | ||
} | ||
|
||
|
||
def _make_token_url(user, token_type): | ||
token_generator = EMAIL_MAPPING[token_type]["token_generator"] | ||
user.last_token_sent_at = timezone.now() | ||
user.save() | ||
token = token_generator.make_token(user) | ||
base_url = settings.BASE_URL | ||
url_path = reverse(EMAIL_MAPPING[token_type]["url_name"]) | ||
url = str(furl.furl(url=base_url, path=url_path, query_params={"code": token, "user_id": str(user.id)})) | ||
return url | ||
|
||
|
||
def _send_token_email(user, token_type): | ||
url = _make_token_url(user, token_type) | ||
context = dict(user=user, url=url, contact_address=settings.CONTACT_EMAIL) | ||
body = render_to_string(EMAIL_MAPPING[token_type]["template_name"], context) | ||
response = send_mail( | ||
subject=EMAIL_MAPPING[token_type]["subject"], | ||
message=body, | ||
from_email=settings.FROM_EMAIL, | ||
recipient_list=[user.email], | ||
) | ||
return response | ||
|
||
|
||
def _send_normal_email(subject, template_name, to_address, context): | ||
body = render_to_string(template_name, context) | ||
response = send_mail( | ||
subject=subject, | ||
message=body, | ||
from_email=settings.FROM_EMAIL, | ||
recipient_list=[to_address], | ||
) | ||
return response | ||
|
||
|
||
def send_password_reset_email(user): | ||
return _send_token_email(user, "password-reset") | ||
|
||
|
||
def send_invite_email(user): | ||
user.invited_at = timezone.now() | ||
user.save() | ||
return _send_token_email(user, "invite-user") | ||
|
||
|
||
def send_verification_email(user): | ||
return _send_token_email(user, "email-verification") | ||
|
||
|
||
def send_register_email(user): | ||
return _send_token_email(user, "email-register") | ||
|
||
|
||
def send_account_already_exists_email(user): | ||
data = EMAIL_MAPPING["account-already-exists"] | ||
base_url = settings.BASE_URL | ||
reset_url = furl.furl(url=base_url) | ||
reset_url.path.add(data["url_name"]) | ||
reset_url = str(reset_url) | ||
context = {"contact_address": settings.CONTACT_EMAIL, "url": base_url, "reset_link": reset_url} | ||
response = _send_normal_email( | ||
subject=data["subject"], | ||
template_name=data["template_name"], | ||
to_address=user.email, | ||
context=context, | ||
) | ||
return response | ||
|
||
|
||
def verify_token(user_id, token, token_type): | ||
user = models.User.objects.get(id=user_id) | ||
result = EMAIL_MAPPING[token_type]["token_generator"].check_token(user, token) | ||
return result |
22 changes: 22 additions & 0 deletions
22
consultation-analyser/consultation_analyser/consultations/info_views.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,22 @@ | ||
""" | ||
Views for info pages like privacy notice, accessibility statement, etc. | ||
These shouldn't contain sensitive data and don't require login. | ||
""" | ||
|
||
from django.shortcuts import render | ||
from django.views.decorators.http import require_http_methods | ||
|
||
|
||
@require_http_methods(["GET"]) | ||
def privacy_notice_view(request): | ||
return render(request, "privacy-notice.html", {}) | ||
|
||
|
||
@require_http_methods(["GET"]) | ||
def support_view(request): | ||
return render(request, "support.html", {}) | ||
|
||
|
||
@require_http_methods(["GET"]) | ||
def accessibility_statement_view(request): | ||
return render(request, "accessibility-statement.html", {}) |
39 changes: 39 additions & 0 deletions
39
...alyser/consultation_analyser/consultations/management/commands/assign_superuser_status.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,39 @@ | ||
from django.core.management import BaseCommand | ||
from django_otp.plugins.otp_totp.models import TOTPDevice | ||
|
||
from consultation_analyser.consultations.models import User | ||
|
||
|
||
class Command(BaseCommand): | ||
help = """This should be run once per environment to set the initial superuser. | ||
Thereafter the superuser should assign new staff users via the admin and send | ||
them the link to the Authenticator. | ||
Once run this command will return the link to a Time-One-Time-Pass that the | ||
superuser should use to enable login to the admin portal.""" | ||
|
||
def add_arguments(self, parser): | ||
parser.add_argument("-e", "--email", type=str, help="user's email", required=True) | ||
parser.add_argument("-p", "--password", type=str, help="user's new password") | ||
|
||
def handle(self, *args, **kwargs): | ||
email = kwargs["email"] | ||
password = kwargs["password"] | ||
|
||
user, _ = User.objects.get_or_create(email=email) | ||
|
||
user.is_superuser = True | ||
user.is_staff = True | ||
if password: | ||
user.set_password(password) | ||
|
||
user.save() | ||
user.refresh_from_db() | ||
|
||
if not user.password: | ||
self.stderr.write(self.style.ERROR(f"A password must be set for '{email}'.")) | ||
return | ||
|
||
device, _ = TOTPDevice.objects.get_or_create(user=user, confirmed=True, tolerance=0) | ||
self.stdout.write(device.config_url) | ||
return |
Oops, something went wrong.