Skip to content

Commit e16d568

Browse files
authored
ci: merge main to release (#9065)
2 parents e5403bb + 3241d57 commit e16d568

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2279
-329
lines changed

.github/workflows/build-base-app.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959
echo "${{ env.IMGVERSION }}" > dev/build/TARGET_BASE
6060
6161
- name: Commit CHANGELOG.md
62-
uses: stefanzweifel/git-auto-commit-action@v5
62+
uses: stefanzweifel/git-auto-commit-action@v6
6363
with:
6464
branch: ${{ github.ref_name }}
6565
commit_message: 'ci: update base image target version to ${{ env.IMGVERSION }}'

dev/build/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM ghcr.io/ietf-tools/datatracker-app-base:20250514T1627
1+
FROM ghcr.io/ietf-tools/datatracker-app-base:20250624T1543
22
LABEL maintainer="IETF Tools Team <[email protected]>"
33

44
ENV DEBIAN_FRONTEND=noninteractive

dev/build/TARGET_BASE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
20250514T1627
1+
20250624T1543

docker/README.md

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Datatracker Development in Docker
22

3+
- [Getting started](#getting-started)
4+
- [Using Visual Studio Code](#using-visual-studio-code)
5+
- [Initial Setup](#initial-setup)
6+
- [Subsequent Launch](#subsequent-launch)
7+
- [Usage](#usage)
8+
- [Using Other Editors / Generic](#using-other-editors--generic)
9+
- [Exit Environment](#exit-environment)
10+
- [Accessing PostgreSQL Port](#accessing-postgresql-port)
11+
- [Clean and Rebuild DB from latest image](#clean-and-rebuild-db-from-latest-image)
12+
- [Clean all](#clean-all)
13+
- [Updating an older environment](#updating-an-older-environment)
14+
- [Notes / Troubleshooting](#notes--troubleshooting)
15+
316
## Getting started
417

518
1. [Set up Docker](https://docs.docker.com/get-started/) on your preferred platform. On Windows, it is highly recommended to use the [WSL 2 *(Windows Subsystem for Linux)*](https://docs.docker.com/desktop/windows/wsl/) backend.
@@ -123,7 +136,14 @@ docker compose down
123136

124137
to terminate the containers.
125138

126-
### Clean and Rebuild DB from latest image
139+
### Accessing PostgreSQL Port
140+
141+
The port is exposed but not automatically mapped to `5432` to avoid potential conflicts with the host. To get the mapped port, run the command *(from the project `/docker` directory)*:
142+
```sh
143+
docker compose port db 5432
144+
```
145+
146+
## Clean and Rebuild DB from latest image
127147

128148
To delete the active DB container, its volume and get the latest image / DB dump, simply run the following command:
129149

@@ -141,7 +161,7 @@ docker compose pull db
141161
docker compose build --no-cache db
142162
```
143163

144-
### Clean all
164+
## Clean all
145165

146166
To delete all containers for this project, its associated images and purge any remaining dangling images, simply run the following command:
147167

@@ -157,17 +177,20 @@ On Windows:
157177
docker compose down -v --rmi all
158178
docker image prune
159179
```
160-
### Updating an older environment
180+
181+
## Updating an older environment
161182

162183
If you already have a clone, such as from a previous codesprint, and are updating that clone, before starting the datatracker from the updated image:
163-
* rm ietf/settings_local.py # The startup script will put a new one, appropriate to the current release, in place
164-
* Execute the `Clean all` sequence above.
184+
1. `rm ietf/settings_local.py` *(The startup script will put a new one, appropriate to the current release, in place)*
185+
1. Execute the [Clean all](#clean-all) sequence above.
165186

166-
### Accessing PostgreSQL Port
187+
If the dev environment fails to start, even after running the [Clean all](#clean-all) sequence above, you can fully purge all docker cache, containers, images and volumes by running the command below.
188+
189+
> [!CAUTION]
190+
> Note that this will delete everything docker-related, including non-datatracker docker resources you might have.
167191

168-
The port is exposed but not automatically mapped to `5432` to avoid potential conflicts with the host. To get the mapped port, run the command *(from the project `/docker` directory)*:
169192
```sh
170-
docker compose port db 5432
193+
docker system prune -a --volumes
171194
```
172195

173196
## Notes / Troubleshooting

ietf/group/migrations/0005_remove_sdo_authorized_individuals.py

Lines changed: 192 additions & 0 deletions
Large diffs are not rendered by default.

ietf/group/migrations/0006_remove_liason_contacts.py

Lines changed: 270 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright The IETF Trust 2025, All Rights Reserved
2+
3+
from django.db import migrations
4+
5+
6+
def forward(apps, schema_editor):
7+
Group = apps.get_model("group", "Group")
8+
GroupFeatures = apps.get_model("group", "GroupFeatures")
9+
iab = Group.objects.get(acronym="iab")
10+
iab.used_roles = [
11+
"chair",
12+
"delegate",
13+
"exofficio",
14+
"liaison",
15+
"liaison_coordinator",
16+
"member",
17+
]
18+
iab.save()
19+
GroupFeatures.objects.filter(type_id="ietf").update(
20+
default_used_roles=[
21+
"ad",
22+
"member",
23+
"comdir",
24+
"delegate",
25+
"execdir",
26+
"recman",
27+
"secr",
28+
"chair",
29+
]
30+
)
31+
32+
33+
def reverse(apps, schema_editor):
34+
Group = apps.get_model("group", "Group")
35+
iab = Group.objects.get(acronym="iab")
36+
iab.used_roles = []
37+
iab.save()
38+
# Intentionally not putting trac-* back into grouptype ietf default_used_roles
39+
40+
41+
class Migration(migrations.Migration):
42+
dependencies = [
43+
("group", "0006_remove_liason_contacts"),
44+
("name", "0018_alter_rolenames"),
45+
]
46+
47+
operations = [
48+
migrations.RunPython(forward, reverse),
49+
]

ietf/ietfauth/forms.py

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@
33

44

55
import re
6+
67
from unidecode import unidecode
78

89
from django import forms
10+
from django.contrib.auth.models import User
11+
from django.contrib.auth import password_validation
912
from django.core.exceptions import ValidationError
1013
from django.db import models
11-
from django.contrib.auth.models import User
1214

1315
from ietf.person.models import Person, Email
1416
from ietf.mailinglists.models import Allowlisted
1517
from ietf.utils.text import isascii
18+
from .password_validation import StrongPasswordValidator
1619

1720
from .validators import prevent_at_symbol, prevent_system_name, prevent_anonymous_name, is_allowed_address
1821
from .widgets import PasswordStrengthInput, PasswordConfirmationInput
@@ -170,33 +173,52 @@ class Meta:
170173
model = Allowlisted
171174
exclude = ['by', 'time' ]
172175

173-
174-
from django import forms
175-
176176

177177
class ChangePasswordForm(forms.Form):
178178
current_password = forms.CharField(widget=forms.PasswordInput)
179179

180-
new_password = forms.CharField(widget=PasswordStrengthInput(attrs={'class':'password_strength'}))
181-
new_password_confirmation = forms.CharField(widget=PasswordConfirmationInput(
182-
confirm_with='new_password',
183-
attrs={'class':'password_confirmation'}))
180+
new_password = forms.CharField(
181+
widget=PasswordStrengthInput(
182+
attrs={
183+
"class": "password_strength",
184+
"data-disable-strength-enforcement": "", # usually removed in init
185+
}
186+
),
187+
)
188+
new_password_confirmation = forms.CharField(
189+
widget=PasswordConfirmationInput(
190+
confirm_with="new_password", attrs={"class": "password_confirmation"}
191+
)
192+
)
184193

185194
def __init__(self, user, data=None):
186195
self.user = user
187-
super(ChangePasswordForm, self).__init__(data)
196+
super().__init__(data)
197+
# Check whether we have validators to enforce
198+
new_password_field = self.fields["new_password"]
199+
for pwval in password_validation.get_default_password_validators():
200+
if isinstance(pwval, password_validation.MinimumLengthValidator):
201+
new_password_field.widget.attrs["minlength"] = pwval.min_length
202+
elif isinstance(pwval, StrongPasswordValidator):
203+
new_password_field.widget.attrs.pop(
204+
"data-disable-strength-enforcement", None
205+
)
188206

189207
def clean_current_password(self):
190-
password = self.cleaned_data.get('current_password', None)
208+
# n.b., password = None is handled by check_password and results in a failed check
209+
password = self.cleaned_data.get("current_password", None)
191210
if not self.user.check_password(password):
192-
raise ValidationError('Invalid password')
211+
raise ValidationError("Invalid password")
193212
return password
194-
213+
195214
def clean(self):
196-
new_password = self.cleaned_data.get('new_password', None)
197-
conf_password = self.cleaned_data.get('new_password_confirmation', None)
198-
if not new_password == conf_password:
199-
raise ValidationError("The password confirmation is different than the new password")
215+
new_password = self.cleaned_data.get("new_password", "")
216+
conf_password = self.cleaned_data.get("new_password_confirmation", "")
217+
if new_password != conf_password:
218+
raise ValidationError(
219+
"The password confirmation is different than the new password"
220+
)
221+
password_validation.validate_password(conf_password, self.user)
200222

201223

202224
class ChangeUsernameForm(forms.Form):
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright The IETF Trust 2025, All Rights Reserved
2+
from django.core.exceptions import ValidationError
3+
from zxcvbn import zxcvbn
4+
5+
6+
class StrongPasswordValidator:
7+
message = "This password does not meet complexity requirements and is easily guessable."
8+
code = "weak"
9+
min_zxcvbn_score = 3
10+
11+
def __init__(self, message=None, code=None, min_zxcvbn_score=None):
12+
if message is not None:
13+
self.message = message
14+
if code is not None:
15+
self.code = code
16+
if min_zxcvbn_score is not None:
17+
self.min_zxcvbn_score = min_zxcvbn_score
18+
19+
def validate(self, password, user=None):
20+
"""Validate that a password is strong enough"""
21+
strength_report = zxcvbn(password[:72], max_length=72)
22+
if strength_report["score"] < self.min_zxcvbn_score:
23+
raise ValidationError(message=self.message, code=self.code)

0 commit comments

Comments
 (0)