* 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
This commit is contained in:
parent
66533827e1
commit
9b4903e2e6
|
@ -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')
|
||||
|
|
|
@ -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<doc>[-\w]+)/(?P<type>[-\w]+)/?$', views.state),
|
||||
url(r'^state/(?P<doc>[-\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'),
|
||||
]
|
||||
|
||||
|
|
|
@ -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 + ' <a href="#pi" aria-label="!"><i class="bi bi-exclamation-circle"></i></a>')
|
||||
|
||||
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")
|
||||
|
|
|
@ -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')
|
||||
self.assertFalse(has_role(role.person.user, []), 'has_role() should return False when role_name is empty')
|
||||
|
|
|
@ -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 <a href="{urlreverse("ietf.ietfauth.views.profile")}">account profile</a> 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
|
||||
|
|
|
@ -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"]
|
||||
|
|
21
ietf/person/migrations/0026_drop_consent.py
Normal file
21
ietf/person/migrations/0026_drop_consent.py
Normal file
|
@ -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',
|
||||
),
|
||||
]
|
18
ietf/person/migrations/0027_personevent_drop_consent.py
Normal file
18
ietf/person/migrations/0027_personevent_drop_consent.py
Normal file
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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 = [
|
|
@ -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"),
|
||||
]
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -104,12 +104,6 @@
|
|||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a class="dropdown-item {% if flavor != 'top' %} text-wrap link-primary{% endif %}"
|
||||
href="{% url 'personal-information' %}">
|
||||
Handling of personal information
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if not request.user.is_authenticated %}
|
||||
<li>
|
||||
|
|
|
@ -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 %}
|
||||
<h1>Personal Information in the Datatracker</h1>
|
||||
<p>
|
||||
<a href="{% url 'ietf.doc.views_doc.document_html' name='rfc3935' %}">RFC 3935, "A Mission Statement for the IETF"</a>
|
||||
lays out
|
||||
the goal and the mission of the IETF as follows
|
||||
</p>
|
||||
<pre>
|
||||
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.
|
||||
|
||||
</pre>
|
||||
{% 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 %}
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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
|
||||
<a href="https://www.ietf.org/about/note-well/">"Note Well"</a>
|
||||
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
|
||||
<a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32016R0679#d1e1888-1-1">
|
||||
EU's General Data Protection Regulation
|
||||
</a>.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Personal photograph or other likeness;</li>
|
||||
<li>Personal biography;</li>
|
||||
<li>Personal email addresses not derived from IETF contributions; and</li>
|
||||
<li>Personal account login information</li>
|
||||
<li>Personal notification subscriptions</li>
|
||||
</ul>
|
||||
<p>
|
||||
Most of this information can be edited on the individual's
|
||||
<a href="{% url 'ietf.ietfauth.views.profile' %}">Account Info</a>
|
||||
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
|
||||
<a href="mailto:{{ settings.SECRETARIAT_ACTION_EMAIL }}">IETF secretariat</a>
|
||||
to change
|
||||
or remove the information will be honoured to the extent feasible and
|
||||
legally permitted.
|
||||
</p>
|
||||
<p>
|
||||
Please also see the IETF's overall
|
||||
<a href="https://www.ietf.org/privacy-statement/">Statement concerning personal data.</a>
|
||||
</p>
|
||||
{% endblock %}
|
|
@ -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 %}
|
||||
<h1>Profile update successful</h1>
|
||||
<h1>Account information update successful</h1>
|
||||
<p class="my-3">
|
||||
Your account has been updated to reflect the changes you submitted.
|
||||
</p>
|
||||
|
@ -14,5 +14,5 @@
|
|||
</p>
|
||||
{% endfor %}
|
||||
<a class="btn btn-primary"
|
||||
href="{% url "ietf.ietfauth.views.profile" %}">Edit profile</a>
|
||||
href="{% url "ietf.ietfauth.views.profile" %}">Edit account information</a>
|
||||
{% endblock %}
|
|
@ -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 %}
|
||||
<h1>Profile information for {{ user.person.name }}</h1>
|
||||
<p class="my-3">
|
||||
The information you provide below is used to generate your
|
||||
<a href="{% url 'ietf.person.views.profile' email_or_name=user.person.email %}">public datatracker profile page</a>
|
||||
</p>
|
||||
<h1>Your account</h1>
|
||||
<form class="form-horizontal" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors person_form %}
|
||||
|
@ -20,9 +16,6 @@
|
|||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label fw-bold">
|
||||
User name
|
||||
<a href="#pi" aria-label="!">
|
||||
<i class="bi bi-exclamation-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<p class="form-control-plaintext">
|
||||
|
@ -34,9 +27,6 @@
|
|||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label fw-bold">
|
||||
Password
|
||||
<a href="#pi" aria-label="!">
|
||||
<i class="bi bi-exclamation-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<a class="btn btn-primary" href="{% url 'ietf.ietfauth.views.change_password' %}">Change password</a>
|
||||
|
@ -46,9 +36,6 @@
|
|||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label fw-bold">
|
||||
Photo
|
||||
<a href="#pi" aria-label="!">
|
||||
<i class="bi bi-exclamation-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
<div class="col-sm-10">{% include "person/photo.html" with person=person %}</div>
|
||||
</div>
|
||||
|
@ -93,12 +80,20 @@
|
|||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label fw-bold">
|
||||
Published information
|
||||
</label>
|
||||
<p class="col-sm-10">
|
||||
All of the data below {% if person.photo %}and your photo {% endif %}
|
||||
may be openly published in Datatracker, Meetecho and other IT systems.
|
||||
</p>
|
||||
</div>
|
||||
{% bootstrap_form person_form layout="horizontal" exclude="ascii_short" %}
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label fw-bold">
|
||||
External resources
|
||||
<a href="#pi" aria-label="!">
|
||||
<i class="bi bi-exclamation-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
{% if person.personextresource_set.all %}
|
||||
|
@ -121,6 +116,14 @@
|
|||
</div>
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label fw-bold">Email addresses</label>
|
||||
<p class="col-sm-10">
|
||||
You can optionally link more email addresses to your account,
|
||||
which will then ensure that all I-Ds you have published using
|
||||
different email addresses are all shown against your account.
|
||||
</p>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-2"></div>
|
||||
<div class="col-sm-10 emails">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
|
@ -152,11 +155,6 @@
|
|||
{% if email.active %}checked{% endif %}>
|
||||
</td>
|
||||
<td>
|
||||
{% if email.origin == person.user.username or email.origin == '' %}
|
||||
<a href="#pi" aria-label="!">
|
||||
<i class="bi bi-exclamation-circle"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{{ email|linkify }}
|
||||
</td>
|
||||
<td>{{ email.origin|default:'(unknown)'|urlize_ietf_docs }}</td>
|
||||
|
@ -172,56 +170,23 @@
|
|||
{% endfor %}
|
||||
<div class="new-emails"></div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary add-email mb-5">Add new email address</button>
|
||||
<button type="button" class="btn btn-primary add-email mt-3 mb-5">Add another email address</button>
|
||||
</div>
|
||||
{% for role in roles %}
|
||||
{% bootstrap_field role.email_form.email show_label=False %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% bootstrap_form person_form layout="horizontal" exclude="ascii_short" %}
|
||||
{% bootstrap_button button_type="submit" content="Submit" extra_classes="offset-sm-2" %}
|
||||
{% bootstrap_button button_type="submit" content="Apply changes" extra_classes="offset-sm-2" %}
|
||||
</form>
|
||||
<div class="form-text mt-5">
|
||||
<p id="pi">
|
||||
<b><i class="bi bi-exclamation-circle"></i> Personal information requiring consent</b>
|
||||
</p>
|
||||
<p>
|
||||
Personal information in the datatracker which is derived from your contributions
|
||||
to the IETF standards development process is covered by the EU General Data Protection
|
||||
Regulation's
|
||||
<a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32016R0679#d1e1888-1-1">
|
||||
Article 6(1) (f)
|
||||
</a>
|
||||
covering IETF's Legitimate Interest due to the IETF's mission of developing standards
|
||||
for the internet. See also the page on
|
||||
<a href="{% url 'personal-information' %}">
|
||||
handling
|
||||
of personal information
|
||||
</a>.
|
||||
</p>
|
||||
<p>
|
||||
Personal information which is <i>not</i> derived from your contributions is covered by the EU
|
||||
<a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32016R0679#d1e1888-1-1">
|
||||
GDPR Article 6(1) (a)
|
||||
</a>
|
||||
regarding consent. All such information is visible on this page and shown with the
|
||||
<i class="bi bi-exclamation-circle"></i> symbol next to it, or listed on your
|
||||
<a href="{% url 'ietf.community.views.view_list' user.username %}">notification subscription page</a>.
|
||||
Most of this
|
||||
information can be edited or removed on these pages. There are some exceptions, such
|
||||
as photos, which currently require an email to
|
||||
<a href="mailto:{{ settings.SECRETARIAT_SUPPORT_EMAIL }}">the Secretariat</a>
|
||||
if you wish to update or remove the information.
|
||||
</p>
|
||||
<p>
|
||||
All the information the datatracker has that is coupled to this account and visible
|
||||
on this page or otherwise related to your work on ietf documents, is also available
|
||||
to you as a
|
||||
<a href="{% url 'ietf.api.views.PersonalInformationExportView' %}">JSON blob</a>
|
||||
when
|
||||
you are logged in. It contains both the Legitimate Interest information and the information
|
||||
that requires Consent.
|
||||
you are logged in.
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -261,10 +226,10 @@
|
|||
.closest("form")
|
||||
.find(".new-emails");
|
||||
|
||||
$('<input class="form-control mb-3" name="new_email" placeholder="You will get a confirmation challenge. To add an address that cannot confirm, contact the secretariat.">')
|
||||
$('<input class="form-control mb-1" name="new_email" placeholder="You will get a confirmation challenge. To add an address that cannot be confirmed this way, contact the secretariat.">')
|
||||
.appendTo(container)
|
||||
.trigger("focus");
|
||||
$('<div class="float-end text-muted small">Remember to submit the form for the new email challenge to be sent.</div>')
|
||||
$('<div class="float-end text-muted small mb-3">Remember to submit the form for the new email challenge to be sent.</div>')
|
||||
.appendTo(container);
|
||||
})
|
||||
});
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
{% load ietf_filters %}{% filter wordwrap:78 %}
|
||||
Dear {{ person.plain_name }},
|
||||
|
||||
We write to address certain personal information stored in your IETF
|
||||
datatracker profile. Going forward, we will maintain and use your personal
|
||||
information only with your consent.
|
||||
|
||||
If you do nothing in response to this email, the information in your profile
|
||||
that requires consent ({{ fields|safe }}) will be deleted {{ days }} days from
|
||||
the date of this email, that is, on {{ date }}. If you later wish to create a
|
||||
new login, you can do so at
|
||||
{{ settings.IDTRACKER_BASE_URL }}{% url 'ietf.ietfauth.views.create_account' %}.
|
||||
|
||||
If you would like us to continue to maintain and process the information that
|
||||
requires your consent, please go to
|
||||
{{ settings.IDTRACKER_BASE_URL }}{% url 'ietf.ietfauth.views.profile' %},
|
||||
and review and edit the information as desired and confirm your consent to our
|
||||
continued maintenance and use of your information by checking the 'Consent'
|
||||
checkbox found at the bottom of the page, and then submit the form.
|
||||
|
||||
For information on how personal information is handled in the datatracker, please see
|
||||
{{ settings.IDTRACKER_BASE_URL }}{% url 'personal-information' %}.
|
||||
|
||||
In case you prefer to not follow any email links, due to phishing
|
||||
considerations, please just go to the datatracker and use the menu
|
||||
entries to log in or create an account. The links above are provided
|
||||
for your convenience, but it works just as well to go the datracker
|
||||
manually and do what's needed.
|
||||
|
||||
Please note that you cannot give consent simply by replying to this email;
|
||||
you must log in to your account and do so there.
|
||||
|
||||
|
||||
Thank You,
|
||||
The IETF Secretariat
|
||||
{% endfilter %}
|
|
@ -1,103 +0,0 @@
|
|||
# Copyright The IETF Trust 2018-2020, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import datetime
|
||||
import time
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.person.models import Person, PersonEvent
|
||||
from ietf.utils.mail import send_mail
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = ("""
|
||||
Send GDPR consent request emails to persons who have not indicated consent
|
||||
to having their personal information stored. Each send is logged as a
|
||||
PersonEvent.
|
||||
|
||||
By default email sending happens at a rate of 1 message per second; the
|
||||
rate can be adjusted with the -r option. At the start of a run, an estimate
|
||||
is given of how many persons to send to, and how long the run will take.
|
||||
|
||||
By default, emails are not sent out if there is less than 6 days since the
|
||||
previous consent request email. The interval can be adjusted with the -m
|
||||
option. One effect of this is that it is possible to break of a run and
|
||||
re-start it with for instance a different rate, without having duplicate
|
||||
messages go out to persons that were handled in the interrupted run.
|
||||
""")
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('-n', '--dry-run', action='store_true', default=False,
|
||||
help="Don't send email, just list recipients")
|
||||
parser.add_argument('-d', '--date', help="Date of deletion (mentioned in message)")
|
||||
parser.add_argument('-m', '--minimum-interval', type=int, default=6,
|
||||
help="Minimum interval between re-sending email messages, default: %(default)s days")
|
||||
parser.add_argument('-r', '--rate', type=float, default=1.0,
|
||||
help='Rate of sending mail, default: %(default)s/s')
|
||||
parser.add_argument('-R', '--reminder', action='store_true', default=False,
|
||||
help='Preface the subject with "Reminder:"')
|
||||
parser.add_argument('user', nargs='*')
|
||||
|
||||
|
||||
def handle(self, *args, **options):
|
||||
# Don't send copies of the whole bulk mailing to the debug mailbox
|
||||
if settings.SERVER_MODE == 'production':
|
||||
settings.EMAIL_COPY_TO = "Email Debug Copy <outbound@ietf.org>"
|
||||
#
|
||||
event_type = 'gdpr_notice_email'
|
||||
# Arguments
|
||||
# --date
|
||||
if 'date' in options and options['date'] != None:
|
||||
try:
|
||||
date = datetime.datetime.strptime(options['date'], "%Y-%m-%d").date()
|
||||
except ValueError as e:
|
||||
raise CommandError('%s' % e)
|
||||
else:
|
||||
date = datetime.date.today() + datetime.timedelta(days=30)
|
||||
days = (date - datetime.date.today()).days
|
||||
if days <= 1:
|
||||
raise CommandError('date must be more than 1 day in the future')
|
||||
# --rate
|
||||
delay = 1.0/options['rate']
|
||||
# --minimum_interval
|
||||
minimum_interval = options['minimum_interval']
|
||||
latest_previous = datetime.datetime.now() - datetime.timedelta(days=minimum_interval)
|
||||
# user
|
||||
self.stdout.write('Querying the database for matching person records ...')
|
||||
if 'user' in options and options['user']:
|
||||
persons = Person.objects.filter(user__username__in=options['user'])
|
||||
else:
|
||||
exclude = PersonEvent.objects.filter(time__gt=latest_previous, type=event_type)
|
||||
persons = Person.objects.exclude(consent=True).exclude(personevent__in=exclude)
|
||||
# Report the size of the run
|
||||
runtime = persons.count() * delay
|
||||
self.stdout.write('Sending to %d users; estimated time a bit more than %d:%02d hours' % (persons.count(), runtime//3600, runtime%3600//60))
|
||||
subject='Personal Information in the IETF Datatracker'
|
||||
if options['reminder']:
|
||||
subject = "Reminder: " + subject
|
||||
for person in persons:
|
||||
fields = ', '.join(person.needs_consent())
|
||||
if fields and person.email_set.exists():
|
||||
if options['dry_run']:
|
||||
print(("%-32s %-32s %-32s %-32s %s" % (person.email(), person.name_from_draft or '', person.name, person.ascii, fields)).encode('utf8'))
|
||||
else:
|
||||
to = [ e.address for e in person.email_set.filter(active=True) ] # pyflakes:ignore
|
||||
if not to:
|
||||
to = [ e.address for e in person.email_set.all() ] # pyflakes:ignore
|
||||
self.stdout.write("Sendimg email to %s" % to)
|
||||
send_mail(None, to, "<gdprnoreply@ietf.org>",
|
||||
subject=subject,
|
||||
template='utils/personal_information_notice.txt',
|
||||
context={
|
||||
'date': date, 'days': days, 'fields': fields,
|
||||
'person': person, 'settings': settings,
|
||||
},
|
||||
)
|
||||
PersonEvent.objects.create(person=person, type='gdpr_notice_email',
|
||||
desc="Sent GDPR notice email to %s with confirmation deadline %s" % (to, date))
|
||||
time.sleep(delay)
|
||||
|
Loading…
Reference in a new issue