From 9b4903e2e6517db534cd475554a2f575d08981ab Mon Sep 17 00:00:00 2001
From: Robert Sparks
Date: Tue, 18 Oct 2022 13:27:19 -0500
Subject: [PATCH] fix: Fix incorrect "GDPR" features/terminology. Fixes #4521.
(#4587)
* fix: remove help/personal-information and the prompt-for-consent email management command.
* fix: remove gdpr treatment except for consent checkbox. Rename Submit.
* fix: drom the consent column from Person and Person.History
* fix: remove the consent boolean. Reorganize the account info form.
* chore: reorder migrations
---
ietf/help/tests_views.py | 6 -
ietf/help/urls.py | 5 +-
ietf/ietfauth/forms.py | 22 +---
ietf/ietfauth/tests.py | 3 +-
ietf/ietfauth/views.py | 19 ----
ietf/person/admin.py | 2 +-
ietf/person/migrations/0026_drop_consent.py | 21 ++++
.../0027_personevent_drop_consent.py | 18 +++
...or.py => 0028_name_character_validator.py} | 4 +-
ietf/person/models.py | 30 +----
ietf/person/utils.py | 2 +-
ietf/templates/base/menu_user.html | 6 -
ietf/templates/help/personal-information.html | 86 ---------------
.../registration/confirm_profile_update.html | 6 +-
ietf/templates/registration/edit_profile.html | 87 +++++----------
.../utils/personal_information_notice.txt | 36 ------
.../commands/send_gdpr_consent_request.py | 103 ------------------
17 files changed, 76 insertions(+), 380 deletions(-)
create mode 100644 ietf/person/migrations/0026_drop_consent.py
create mode 100644 ietf/person/migrations/0027_personevent_drop_consent.py
rename ietf/person/migrations/{0026_name_character_validator.py => 0028_name_character_validator.py} (88%)
delete mode 100644 ietf/templates/help/personal-information.html
delete mode 100644 ietf/templates/utils/personal_information_notice.txt
delete mode 100644 ietf/utils/management/commands/send_gdpr_consent_request.py
diff --git a/ietf/help/tests_views.py b/ietf/help/tests_views.py
index 714dab39e..ee80dad86 100644
--- a/ietf/help/tests_views.py
+++ b/ietf/help/tests_views.py
@@ -19,9 +19,3 @@ class HelpPageTests(TestCase):
for name in names:
if not '-' in name:
self.assertIn(name, content)
-
-
- def test_personal_information_help(self):
- r = self.client.get('/help/personal-information')
- self.assertContains(r, 'personal information')
- self.assertContains(r, 'GDPR')
diff --git a/ietf/help/urls.py b/ietf/help/urls.py
index 9c0bfb157..f1cc625fa 100644
--- a/ietf/help/urls.py
+++ b/ietf/help/urls.py
@@ -1,6 +1,4 @@
-# Copyright The IETF Trust 2013-2018, All Rights Reserved
-
-from django.views.generic import TemplateView
+# Copyright The IETF Trust 2013-2022, All Rights Reserved
from ietf.help import views
from ietf.utils.urls import url
@@ -9,6 +7,5 @@ urlpatterns = [
url(r'^state/(?P[-\w]+)/(?P[-\w]+)/?$', views.state),
url(r'^state/(?P[-\w]+)/?$', views.state),
url(r'^state/?$', views.state_index),
- url(r'^personal-information/?$', TemplateView.as_view(template_name='help/personal-information.html'), name='personal-information'),
]
diff --git a/ietf/ietfauth/forms.py b/ietf/ietfauth/forms.py
index 851ad02f8..d46ad54db 100644
--- a/ietf/ietfauth/forms.py
+++ b/ietf/ietfauth/forms.py
@@ -103,10 +103,7 @@ def get_person_form(*args, **kwargs):
class PersonForm(forms.ModelForm):
class Meta:
model = Person
- exclude = exclude_list
- widgets = {
- 'consent': forms.widgets.CheckboxInput,
- }
+ exclude = exclude_list
def __init__(self, *args, **kwargs):
super(PersonForm, self).__init__(*args, **kwargs)
@@ -120,10 +117,6 @@ def get_person_form(*args, **kwargs):
self.fields['pronouns_selectable'] = forms.MultipleChoiceField(label='Pronouns', choices = [(option, option) for option in ["he/him", "she/her", "they/them"]], widget=forms.CheckboxSelectMultiple, required=False)
- for f in ['name', 'ascii', 'ascii_short', 'biography', 'photo', 'photo_thumb', 'pronouns_selectable']:
- if f in self.fields:
- self.fields[f].label = mark_safe(self.fields[f].label + ' ')
-
self.unidecoded_ascii = False
if self.data and not self.data.get("ascii", "").strip():
@@ -155,19 +148,6 @@ def get_person_form(*args, **kwargs):
prevent_system_name(name)
return ascii_cleaner(name)
- def clean_consent(self):
- consent = self.cleaned_data.get('consent')
- require_consent = (
- self.cleaned_data.get('name') != person.name_from_draft
- or self.cleaned_data.get('ascii') != person.name_from_draft
- or self.cleaned_data.get('biography')
- or self.cleaned_data.get('pronouns_selectable')
- or self.cleaned_data.get('pronouns_freetext')
- )
- if consent == False and require_consent:
- raise forms.ValidationError("In order to modify your profile with data that require consent, you must permit the IETF to use the uploaded data.")
- return consent
-
def clean(self):
if self.cleaned_data.get("pronouns_selectable") and self.cleaned_data.get("pronouns_freetext"):
self.add_error("pronouns_freetext", "Either select from the pronoun checkboxes or provide a custom value, but not both")
diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py
index 1651de08d..b6c145e68 100644
--- a/ietf/ietfauth/tests.py
+++ b/ietf/ietfauth/tests.py
@@ -252,7 +252,6 @@ class IetfAuthTests(TestCase):
"pronouns_freetext": "foo/bar",
"affiliation": "Test Org",
"active_emails": email_address,
- "consent": True,
}
# edit details - faulty ASCII
@@ -1011,4 +1010,4 @@ class UtilsTests(TestCase):
"""has_role is False if role_names is empty"""
role = RoleFactory(name_id='secr', group__acronym='secretariat')
self.assertTrue(has_role(role.person.user, ['Secretariat']), 'Test is broken')
- self.assertFalse(has_role(role.person.user, []), 'has_role() should return False when role_name is empty')
\ No newline at end of file
+ self.assertFalse(has_role(role.person.user, []), 'has_role() should return False when role_name is empty')
diff --git a/ietf/ietfauth/views.py b/ietf/ietfauth/views.py
index 8cf21f52c..e405d6620 100644
--- a/ietf/ietfauth/views.py
+++ b/ietf/ietfauth/views.py
@@ -54,7 +54,6 @@ from django.contrib.auth.views import LoginView
from django.contrib.sites.models import Site
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.urls import reverse as urlreverse
-from django.utils.safestring import mark_safe
from django.http import Http404, HttpResponseRedirect, HttpResponseForbidden
from django.shortcuts import render, redirect, get_object_or_404
from django.utils.encoding import force_bytes
@@ -699,7 +698,6 @@ def login(request, extra_context=None):
which is not recognized as a valid password hash.
"""
- require_consent = []
if request.method == "POST":
form = AuthenticationForm(request, data=request.POST)
username = form.data.get('username')
@@ -721,11 +719,6 @@ def login(request, extra_context=None):
user = u2
#
if user:
- try:
- if user.person and not user.person.consent:
- require_consent = user.person.needs_consent()
- except ObjectDoesNotExist:
- pass
try:
identify_hasher(user.password)
except ValueError:
@@ -742,18 +735,6 @@ def login(request, extra_context=None):
except Person.DoesNotExist:
logout(request)
response = render(request, 'registration/missing_person.html')
- if require_consent:
- messages.warning(request, mark_safe(f'''
-
- You have personal information associated with your account which is not
- derived from draft submissions or other ietf work, namely: %s. Please go
- to your account profile and review your
- personal information, then scoll to the bottom and check the 'confirm'
- checkbox and submit the form, in order to to indicate that that the
- provided personal information may be used and displayed within the IETF
- datatracker.
-
- ''' % ', '.join(require_consent)))
return response
@login_required
diff --git a/ietf/person/admin.py b/ietf/person/admin.py
index 3569c0334..cd8ca2abf 100644
--- a/ietf/person/admin.py
+++ b/ietf/person/admin.py
@@ -36,7 +36,7 @@ class PersonAdmin(simple_history.admin.SimpleHistoryAdmin):
prefix, first, middle, last, suffix = name_parts(obj.name)
return "%s %s" % (first, last)
list_display = ["name", "short", "plain_name", "time", "user", ]
- fields = ("user", "time", "name", "plain", "name_from_draft", "ascii", "ascii_short", "pronouns_selectable", "pronouns_freetext", "biography", "photo", "photo_thumb", "consent",)
+ fields = ("user", "time", "name", "plain", "name_from_draft", "ascii", "ascii_short", "pronouns_selectable", "pronouns_freetext", "biography", "photo", "photo_thumb",)
readonly_fields = ("name_from_draft", )
search_fields = ["name", "ascii"]
raw_id_fields = ["user"]
diff --git a/ietf/person/migrations/0026_drop_consent.py b/ietf/person/migrations/0026_drop_consent.py
new file mode 100644
index 000000000..2acaad678
--- /dev/null
+++ b/ietf/person/migrations/0026_drop_consent.py
@@ -0,0 +1,21 @@
+# Copyright The IETF Trust 2022, All Rights Reserved
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('person', '0025_chat_and_polls_apikey'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='historicalperson',
+ name='consent',
+ ),
+ migrations.RemoveField(
+ model_name='person',
+ name='consent',
+ ),
+ ]
diff --git a/ietf/person/migrations/0027_personevent_drop_consent.py b/ietf/person/migrations/0027_personevent_drop_consent.py
new file mode 100644
index 000000000..e2443596f
--- /dev/null
+++ b/ietf/person/migrations/0027_personevent_drop_consent.py
@@ -0,0 +1,18 @@
+# Copyright The IETF Trust 2022, All Rights Reserved
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('person', '0026_drop_consent'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='personevent',
+ name='type',
+ field=models.CharField(choices=[('apikey_login', 'API key login'), ('email_address_deactivated', 'Email address deactivated')], max_length=50),
+ ),
+ ]
diff --git a/ietf/person/migrations/0026_name_character_validator.py b/ietf/person/migrations/0028_name_character_validator.py
similarity index 88%
rename from ietf/person/migrations/0026_name_character_validator.py
rename to ietf/person/migrations/0028_name_character_validator.py
index 34fb5851f..c7e4a9305 100644
--- a/ietf/person/migrations/0026_name_character_validator.py
+++ b/ietf/person/migrations/0028_name_character_validator.py
@@ -1,4 +1,4 @@
-# Generated by Django 2.2.28 on 2022-10-17 10:18
+# Copyright The IETF Trust 2022, All Rights Reserved
from django.db import migrations, models
import ietf.person.models
@@ -7,7 +7,7 @@ import ietf.person.models
class Migration(migrations.Migration):
dependencies = [
- ('person', '0025_chat_and_polls_apikey'),
+ ('person', '0027_personevent_drop_consent'),
]
operations = [
diff --git a/ietf/person/models.py b/ietf/person/models.py
index c3c767739..c8982e338 100644
--- a/ietf/person/models.py
+++ b/ietf/person/models.py
@@ -13,7 +13,7 @@ from urllib.parse import urljoin
from django.conf import settings
from django.contrib.auth.models import User
-from django.core.exceptions import ObjectDoesNotExist, ValidationError
+from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.db import models
from django.template.loader import render_to_string
@@ -58,7 +58,6 @@ class Person(models.Model):
photo = models.ImageField(storage=NoLocationMigrationFileSystemStorage(), upload_to=settings.PHOTOS_DIRNAME, blank=True, default=None)
photo_thumb = models.ImageField(storage=NoLocationMigrationFileSystemStorage(), upload_to=settings.PHOTOS_DIRNAME, blank=True, default=None)
name_from_draft = models.CharField("Full Name (from submission)", null=True, max_length=255, editable=False, help_text="Name as found in a draft submission.")
- consent = models.BooleanField("I hereby give my consent to the use of the personal details I have provided (photo, bio, name, pronouns, email) within the IETF Datatracker", null=True, default=None)
def __str__(self):
return self.plain_name()
@@ -194,32 +193,6 @@ class Person(models.Model):
from ietf.doc.models import Document
return Document.objects.filter(documentauthor__person=self, type='draft', states__slug__in=['repl', 'expired', 'auth-rm', 'ietf-rm']).distinct().order_by('-time')
- def needs_consent(self):
- """
- Returns an empty list or a list of fields which holds information that
- requires consent to be given.
- """
- needs_consent = []
- if self.name != self.name_from_draft:
- needs_consent.append("full name")
- if self.ascii != self.name_from_draft:
- needs_consent.append("ascii name")
- if self.biography and not (self.role_set.exists() or self.rolehistory_set.exists()):
- needs_consent.append("biography")
- if self.user_id:
- needs_consent.append("login")
- try:
- if self.user.communitylist_set.exists():
- needs_consent.append("draft notification subscription(s)")
- except ObjectDoesNotExist:
- pass
- for email in self.email_set.all():
- if not email.origin.split(':')[0] in ['author', 'role', 'reviewer', 'liaison', 'shepherd', ]:
- needs_consent.append("email address(es)")
- break
- if self.pronouns_freetext or self.pronouns_selectable:
- needs_consent.append("pronouns")
- return needs_consent
def save(self, *args, **kwargs):
created = not self.pk
@@ -428,7 +401,6 @@ class PersonalApiKey(models.Model):
PERSON_EVENT_CHOICES = [
("apikey_login", "API key login"),
- ("gdpr_notice_email", "GDPR consent request email sent"),
("email_address_deactivated", "Email address deactivated"),
]
diff --git a/ietf/person/utils.py b/ietf/person/utils.py
index 54c2f5dbe..950c19841 100755
--- a/ietf/person/utils.py
+++ b/ietf/person/utils.py
@@ -40,7 +40,7 @@ def merge_persons(request, source, target, file=sys.stdout, verbose=False):
dedupe_aliases(target)
# copy other attributes
- for field in ('ascii','ascii_short', 'biography', 'photo', 'photo_thumb', 'name_from_draft', 'consent'):
+ for field in ('ascii','ascii_short', 'biography', 'photo', 'photo_thumb', 'name_from_draft'):
if getattr(source,field) and not getattr(target,field):
setattr(target,field,getattr(source,field))
target.save()
diff --git a/ietf/templates/base/menu_user.html b/ietf/templates/base/menu_user.html
index 1b1e44482..addab232f 100644
--- a/ietf/templates/base/menu_user.html
+++ b/ietf/templates/base/menu_user.html
@@ -104,12 +104,6 @@
{% endif %}
-
-
- Handling of personal information
-
-
{% endif %}
{% if not request.user.is_authenticated %}
diff --git a/ietf/templates/help/personal-information.html b/ietf/templates/help/personal-information.html
deleted file mode 100644
index d60ee93b6..000000000
--- a/ietf/templates/help/personal-information.html
+++ /dev/null
@@ -1,86 +0,0 @@
-{# Copyright The IETF Trust 2018-2018, All Rights Reserved #}
-{% extends "base.html" %}
-{% load origin %}
-{% block title %}
- Personal Information in the Datatracker
-{% endblock %}
-{% block content %}
- {% origin %}
- Personal Information in the Datatracker
-
- RFC 3935, "A Mission Statement for the IETF"
- lays out
- the goal and the mission of the IETF as follows
-
-
- The goal of the IETF is to make the Internet work better.
-
- The mission of the IETF is to produce high quality, relevant
- technical and engineering documents that influence the way people
- design, use, and manage the Internet in such a way as to make the
- Internet work better. These documents include protocol standards,
- best current practices, and informational documents of various kinds.
-
-
- {% comment %}
- The following text has been reviewed by legal counsel (Thomas Zych)
- on Thu, 26 Apr 2018 17:24:03 -0400
- *** DO NOT CHANGE WITHOUT LEGAL REVIEW ***
-{% endcomment %}
-
- In order to fulfill its mission, the IETF provides various ways to distribute
- and discuss Internet-Drafts, and otherwise process them until there is
- consensus that they are ready for publication.
-
-
- This makes the information in the content of the draft documents, as well
- as contributions related to the draft documents and their processing as
- laid out in the
- "Note Well"
- statement, of legitimate interest to the IETF when it pursues
- its mission; not only in general terms, but specifically under Article
- 6(1) f) of
-
- EU's General Data Protection Regulation
- .
-
-
- The datatracker treats all personal information derived from draft documents and
- documents published as RFC, as well as personal information derived from
- processing draft documents through the IETF procedures, in accordance with
- applicable law, including the GDPR. This includes author names,
- email addresses and other address information provided in draft documents or as
- contact information for IETF roles such as Working Group chairs, secretaries,
- Area Directors and other roles.
-
-
- There is however additional personal information held in the datatracker that
- is used for other purposes. This information requires the consent of the
- individuals whose information is stored and processed which IETF secures
- through various means, and the person it relates to may at any time request
- its correction or removal. This includes:
-
-
- - Personal photograph or other likeness;
- - Personal biography;
- - Personal email addresses not derived from IETF contributions; and
- - Personal account login information
- - Personal notification subscriptions
-
-
- Most of this information can be edited on the individual's
- Account Info
- page by the individual
- after logging in to the account. If the datatracker holds such
- information about a person, and they don't have an account, a request to
- the
- IETF secretariat
- to change
- or remove the information will be honoured to the extent feasible and
- legally permitted.
-
-
- Please also see the IETF's overall
- Statement concerning personal data.
-
-{% endblock %}
\ No newline at end of file
diff --git a/ietf/templates/registration/confirm_profile_update.html b/ietf/templates/registration/confirm_profile_update.html
index af9fc33c9..259b01081 100644
--- a/ietf/templates/registration/confirm_profile_update.html
+++ b/ietf/templates/registration/confirm_profile_update.html
@@ -1,10 +1,10 @@
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% extends "base.html" %}
{% load origin textfilters %}
-{% block title %}Profile update successful{% endblock %}
+{% block title %}Account information update successful{% endblock %}
{% block content %}
{% origin %}
- Profile update successful
+ Account information update successful
Your account has been updated to reflect the changes you submitted.
@@ -14,5 +14,5 @@
{% endfor %}
Edit profile
+ href="{% url "ietf.ietfauth.views.profile" %}">Edit account information
{% endblock %}
\ No newline at end of file
diff --git a/ietf/templates/registration/edit_profile.html b/ietf/templates/registration/edit_profile.html
index bebf8a924..e16f32daa 100644
--- a/ietf/templates/registration/edit_profile.html
+++ b/ietf/templates/registration/edit_profile.html
@@ -3,14 +3,10 @@
{% load origin %}
{% load widget_tweaks django_bootstrap5 textfilters ietf_filters %}
{% load person_filters %}
-{% block title %}Profile for {{ user }}{% endblock %}
+{% block title %}Your account{% endblock %}
{% block content %}
{% origin %}
- Profile information for {{ user.person.name }}
-
- The information you provide below is used to generate your
- public datatracker profile page
-
+ Your account