Compare commits

...

32 Commits

Author SHA1 Message Date
b62c3104ca dyslectic af 2018-08-03 13:20:23 +02:00
c049394e9e remove binaries 2018-08-02 13:54:36 +02:00
a150374397 fixed some init-saml2-settings bugs and defaults values for that script that make sense for the RUG or at least me. 2018-08-02 13:54:18 +02:00
50d5b9a7b9 added rugwebsite.gdpr_urls 2018-05-17 11:36:03 +02:00
ab56a48dd4 sha256 for chrome 2018-05-16 12:00:24 +02:00
88fb40c54c sha256 for chrome 2018-05-16 11:55:12 +02:00
bd8905313e sha256 for chrome 2018-05-16 11:52:00 +02:00
88a3a6f086 sha256 for chrome 2018-05-16 11:34:58 +02:00
69f3cda470 extracted inline css 2018-05-14 12:00:15 +02:00
27486a4c6d added integrity tags to resources (2) 2018-05-14 11:51:39 +02:00
4b4afb5521 added integrity tags to resources 2018-05-14 11:38:24 +02:00
99e2248626 agree create 3 2018-05-04 17:36:43 +02:00
fc7629edad agree create 3 2018-05-04 17:35:39 +02:00
05b97556c2 agree create 3 2018-05-04 17:34:20 +02:00
7ac1085ae2 agree create 3 2018-05-04 17:28:07 +02:00
1cd3795b87 agree create 3 2018-05-04 17:17:59 +02:00
637f4d2a55 agree create 3 2018-05-04 17:16:56 +02:00
42444c12dc agree create 3 2018-05-04 17:14:46 +02:00
163bda3e30 agree create 3 2018-05-04 17:11:51 +02:00
0e2901b62b agree create 3 2018-05-04 17:09:13 +02:00
fe93d23747 agree create 3 2018-05-04 17:05:18 +02:00
004c3a534f agree create 3 2018-05-04 17:03:10 +02:00
96991610c9 agree create 3 2018-05-04 16:59:35 +02:00
9a4e5ceafa agree create 3 2018-05-04 16:57:43 +02:00
df900898e8 agree create 3 2018-05-04 16:54:26 +02:00
c82c23aac4 agree create 3 2018-05-04 16:53:06 +02:00
36dcc0445b agree create 3 2018-05-04 16:49:41 +02:00
909d232480 agree create 3 2018-05-04 16:48:19 +02:00
de61de4be3 agree create 2 2018-05-04 16:38:35 +02:00
38fbabecaf agree create 2018-05-04 16:37:26 +02:00
37dde44830 agree crate 2018-05-04 16:34:35 +02:00
300b971650 gdpr test coworkers232344 2018-05-04 15:22:10 +02:00
14 changed files with 131 additions and 35 deletions

View File

@@ -1 +1 @@
__version__ = '0.1.30'
__version__ = '0.1.37'

View File

@@ -4,3 +4,7 @@ from django import forms
class RequestGDPRDelete(forms.Form):
email = forms.EmailField(widget=forms.EmailInput)
class GDPRAgreeCreate(forms.Form):
data = forms.CharField(widget=forms.HiddenInput)

11
rugwebsite/gdpr_urls.py Normal file
View File

@@ -0,0 +1,11 @@
from django.conf.urls import url
from rugwebsite.views import gdpr, gdpr_request_delete, gdpr_delete, gdpr_agree, gdpr_create_agree, gdpr_ask_agreement
urlpatterns = [
url(r'/$', gdpr, name='gdpr'),
url(r'request-delete/$', gdpr_request_delete, name='gdpr-request-delete'),
url(r'delete/(?P<email>[^/]+)/(?P<token>[a-zA-Z0-9]{32})/$', gdpr_delete, name='gdpr-delete'),
url(r'agree/(?P<email>[^/]+)/(?P<token>[a-zA-Z0-9]{32})/$', gdpr_agree, name='gdpr-agree'),
url(r'create-agree/$', gdpr_create_agree, name='gdpr-create-agree'),
url(r'ask-for-agreement/$', gdpr_ask_agreement, name='gdpr-ask-for-agreement'),
]

View File

@@ -1,6 +1,7 @@
from django.core.management.base import BaseCommand, CommandError
import datetime
import os.path
import sys
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes, serialization
@@ -18,29 +19,45 @@ class Command(BaseCommand):
'them to the django settings.'
def add_arguments(self, parser):
parser.add_argument('--country', required=True, nargs=1, type=str, dest='country', help='For example: US, NL, DE, etc')
parser.add_argument('--city', required=True, nargs=1, type=str, dest='city', help='For example: London or Groningen')
parser.add_argument('--state', required=True, nargs=1, type=str, dest='state', help='For example: State or province, for example California or Groningen.')
parser.add_argument('--organisation', required=True, nargs=1, type=str, dest='organisation', help='Typically \'University of Groningen\'')
parser.add_argument('--country', required=False, nargs='?', type=str, default=['NL'], dest='country', help='For example: US, NL, DE, etc')
parser.add_argument('--city', required=False, nargs='?', type=str, default=['Groningen'], dest='city', help='For example: London or Groningen')
parser.add_argument('--state', required=False, nargs='?', type=str, default=['Groningen'], dest='state', help='For example: State or province, for example California or Groningen.')
parser.add_argument('--organisation', required=False, nargs='?', type=str, default=['University of Groningen'], dest='organisation', help='Typically \'University of Groningen\'')
parser.add_argument('--organisation-unit', required=True, nargs=1, type=str, dest='organisation_unit', help='For example: \'Research and Innovation\' Support or \'Faculty of smart people\'')
parser.add_argument('--common-name', required=True, nargs=1, type=str, dest='common_name')
parser.add_argument('--alternative', nargs='*', type=str, dest='alternatives')
parser.add_argument('--support-name', required=True, nargs=1, type=str, dest='support_name')
parser.add_argument('--support-email', required=True, nargs=1, type=str, dest='support_email')
parser.add_argument('--technical-name', required=True, nargs=1, type=str, dest='technical_name')
parser.add_argument('--saml-route', required=False, nargs='?', type=str, dest='saml_route', default='sso/saml2/')
parser.add_argument('--technical-email', required=True, nargs=1, type=str, dest='technical_email')
parser.add_argument('--entity-id', required=True, nargs=1, type=str, dest='entity_id', help='Used as an identifyer of your service, typically the url to your service: www.rug.nl/yourservice')
parser.add_argument('--base-url', required=True, nargs=1, type=str, dest='base_url', help='The base url of your service, the route <base_url>sso/saml/ should exist.')
parser.add_argument('--entity-id', required=False, nargs='?', type=str, dest='entity_id', help='Used as an identifyer of your service, typically the url to your service: www.rug.nl/yourservice')
parser.add_argument('--base-url', required=False, nargs='?', type=str, dest='base_url', help='The base url of your service, the route <base_url>sso/saml/ should exist.')
parser.add_argument('--expires-after-days', nargs='?', type=int, default=10 * 365, dest='expires')
def handle(self, *args, **options):
print(options, file=sys.stderr)
for option in {'country', 'city', 'state', 'organisation', 'organisation_unit', 'common_name', 'support_name',
'support_email', 'technical_name', 'technical_email', 'entity_id', 'base_url'}:
'support_email', 'technical_name', 'technical_email'}:
assert option in options and options[option] is not None and len(options[option]) == 1, "Expected one " \
"value for option" \
": " + option
options[option] = options[option][0]
if options['base_url'] is None:
options['base_url'] = 'https://{}/'.format(options['common_name'])
print("# NOTE: deduced --base-url from --common-name: {}".format(options['base_url']), file=sys.stderr)
print("# NOTE: deduced --base-url from --common-name: {}".format(options['base_url']))
else:
options['base_url'] = options['base_url'][0]
if options['entity_id'] is None:
options['entity_id'] = '{}{}{}metadata?provider=RuG'.format(options['base_url'], '' if options['base_url'].endswith('/') else '/', options['saml_route'])
print("# NOTE: deduced --entity-id from --base-url and --saml-route: {}".format(options['entity_id']), file=sys.stderr)
print("# NOTE: deduced --entity-id from --base-url and --saml-route: {}".format(options['entity_id']))
else:
options['entity_id'] = options['entity_id'][0]
key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend = default_backend())
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, options['country']),

View File

@@ -9,6 +9,7 @@ ENTITY_ID = '{entity_id}'
# Important to make sure redirects and such work properly
BASE_URL = '{base_url}'
BASE_URL_SLASH = BASE_URL + ('' if BASE_URL.endswith('/') else '/')
# This support information is used for the SAML2 service provider contact information
TECHNICAL_NAME = '{technical_name}'
@@ -20,16 +21,17 @@ ORGANISATION = '{organisation}'
ORGANISATION_UNIT = '{organisation_unit}'
SAML_ROUTE = BASE_URL + 'sso/saml/'
SAML_ROUTE = '{saml_route}'
SAML_ROUTE_SLASH = SAML_ROUTE + ('' if SAML_ROUTE.endswith('/') else '/')
# redirection after successful SAML2 login
SAML_REDIRECT = BASE_URL + '/'
SAML_REDIRECT = BASE_URL_SLASH
# Mapping used to move the SAML2 attributes to the django-auth user database
SAML_USERS_MAP = [{{
"RuG": {{
"email": dict(key="urn:mace:dir:attribute-def:mail", index=0),
"username": dict(key="urn:mace:dir:attribute-def:uid", index=0),
"first_name": dict(key="urn:mace:dir:attribute-def:gn", index=0),
"first_name": dict(key="urn:mace:dir:attribute-def:givenName", index=0),
"last_name": dict(key="urn:mace:dir:attribute-def:sn", index=0),
}}
}}]
@@ -41,7 +43,7 @@ PRIVATE_KEY = """{private_key}"""
X509 = """{x509}"""
# RuG metadata url, should not change unless you want another service provider.
SAML_PROVIDER_METADATA_URL = 'https://tst-idp.id.rug.nl/nidp/saml2/metadata'
SAML_PROVIDER_METADATA_URL = 'https://signon.rug.nl/nidp/saml2/metadata'
#Code to get the RuG identity provider certificate
import sys
@@ -73,11 +75,11 @@ SAML_PROVIDERS = [{{
"sp": {{
"entityId": ENTITY_ID,
"assertionConsumerService": {{
"url": BASE_URL + "/sso/saml/?provider=RuG&amp;acs",
"url": BASE_URL_SLASH + SAML_ROUTE_SLASH + "?provider=RuG&amp;acs",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
}},
"singleLogoutService": {{
"url": BASE_URL + "/sso/saml/?provider=RuG",
"url": BASE_URL_SLASH + SAML_ROUTE_SLASH + "?provider=RuG",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
}},
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
@@ -85,13 +87,13 @@ SAML_PROVIDERS = [{{
"privateKey": PRIVATE_KEY,
}},
"idp": {{
"entityId": "https://tst-idp.id.rug.nl/nidp/saml2/metadata",
"entityId": "https://signon.rug.nl/nidp/saml2/metadata",
"singleSignOnService": {{
"url": "https://tst-idp.id.rug.nl/nidp/saml2/sso",
"url": "https://signon.rug.nl/nidp/saml2/sso",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
}},
"singleLogoutService": {{
"url": "https://tst-idp.id.rug.nl/nidp/saml2/spslo",
"url": "https://signon.rug.nl/nidp/saml2/spslo",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
}},
"x509cert": RUG_PROVIDER_X509CERT,
@@ -100,7 +102,7 @@ SAML_PROVIDERS = [{{
"en-US": {{
"name": ORGANISATION,
"displayname": ORGANISATION + " / " + ORGANISATION_UNIT,
"url": BASE_URL
"url": BASE_URL_SLASH
}}
}},
"contact_person": {{

View File

@@ -69,6 +69,8 @@ AUTHENTICATION_BACKENDS = [
SAML_ROUTE = 'sso/saml/'
SAML_REDIRECT = '/'
SAML_REDIRECT_CREATED = '/gdpr-just-created/'
SAML_USERS_MAP = []
SAML_PROVIDERS = []

View File

@@ -6,20 +6,18 @@
<meta charset="utf-8"/>
<title>{% block title %}Awesomeness{% endblock %}</title>
<link href='https://fonts.googleapis.com/css?family=Arvo' rel='stylesheet' type='text/css'/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/font-awesome.min.css' %}" />
<link rel="stylesheet" href="{% static 'css/bootstrap-theme.css' %}" />
<link rel="stylesheet" href="{% static 'css/bootstrap-tagsinput.css' %}" />
<link rel="stylesheet" href="{% static 'css/rug-2017.css' %}" />
<link rel="stylesheet" href="{% static 'css/rug-herbert.css' %}" />
<link rel="stylesheet" href="{% static 'css/font-awesome.min.css' %}" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous"/>
<link rel="stylesheet" href="{% static 'css/bootstrap-theme.css' %}" integrity="sha384-s6VGeJubGke2hQrB/YDNi30BNtb6XouaCtN38JpFTI9v6nvNHbI7HGvrtY5OmYuV" crossorigin="anonymous"/>
<link rel="stylesheet" href="{% static 'css/bootstrap-tagsinput.css' %}" integrity="sha384-Rek+1LyOfvb7slWDvUOfdsTFArZbI89fFVHIdMboEsconuw4I7Zt07aknf3GbQS0" crossorigin="anonymous"/>
<link rel="stylesheet" href="{% static 'css/rug-2017.css' %}" integrity="sha384-kJtoxMdHlyhwqatovNuhdOaRZxYa5xq/yFGTOko89Sh4FNrIgyTsLnaI0iexQlfM" crossorigin="anonymous"/>
<link rel="stylesheet" href="{% static 'css/rug-herbert.css' %}" integrity="sha384-sgDr8zcNPlaP2IHJN4OFtZfcD/uVLMWf56lSaLVs+lEZrdCvB3lgOewHakOuVndY" crossorigin="anonymous"/>
<!-- jQuery library -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
<script src="{% static 'js/bootstrap-tagsinput.min.js' %}"></script>
<script src="{% static 'jquery/base.js' %}"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js" integrity="sha384-3ceskX3iaEnIogmQchP8opvBy3Mi7Ce34nWjpBIwVTHfGYWQS9jwHDVRnpKKHJg7" crossorigin="anonymous"></script>
<script src="//code.jquery.com/ui/1.11.4/jquery-ui.js" integrity="sha384-YwCdhNQ2IwiYajqT/nGCj0FiU5SR4oIkzYP3ffzNWtu39GKBddP0M0waDU7Zwco0" crossorigin="anonymous"></script>
<script src="{% static 'js/bootstrap-tagsinput.min.js' %}" integrity="sha384-tdV8t2dotVFchDO14eaQSu2SFqeytqAO84BwLadSgzuLvs6fiFcrqb2CjxDSaUyX" crossorigin="anonymous"></script>
{% block extrahead %}{% endblock %}
</head>

View File

@@ -8,6 +8,8 @@
gekopieerd naar onze database. Op die manier kunt u inloggen en gebruik maken van onze website.
</p>
<p><strong>Als u niet reageert moeten we uw persoonsgegevens verwijderen.</strong></p>
<p>
Samengevat worden de gegevens gebruikt voor authenticatie op onze website en in sommige gevallen voor het
beoordelen van vakgebonden opdrachten die u via onze website maakt. Meer informatie vind u op de volgende pagina.

View File

@@ -6,6 +6,7 @@ persoonsgegevens die wij van u hebben. U heeft in het verleden gebruik gemaakt v
of studentnummer en wachtwoord. Toen zijn uw RUG e-mailadres, voornaam, achternaam en student- of personeelsnummer
gekopieerd naar onze database. Op die manier kunt u inloggen en gebruik maken van onze website.
ALS U NIET REAGEERT MOETEN WE UW PERSOONSGEGEVENS VERWIJDEREN.
Samengevat worden de gegevens gebruikt voor authenticatie op onze website en in sommige gevallen voor het
beoordelen van vakgebonden opdrachten die u via onze website maakt. Meer informatie vind u op de volgende pagina.

View File

@@ -17,6 +17,21 @@
<h1>GDPR</h1>
<p>Privacyverklaring</p>
<br/>
{% if created %}
<p>
U logt voor de eerste keer in en we willen uw persoonsgegevens opslaan. Geeft u daarvoor toestemming?
Als u geen toestemming wilt geven, kunt u deze pagina sluiten.
</p>
<form action="{% url 'gdpr-create-agree' %}" method="post" accept-charset="utf-8" >
{% csrf_token %}
{% bootstrap_form form %}
<button class="btn btn-default" type="submit">Toestemming geven</button>
<br/><br/>
<a href="/">Geen toestemming geven</a>
</form>
<br/>
{% endif %}
<a href="{% url 'gdpr-request-delete' %}">Verzoek tot verwijderen persoonsgegevens</a>
{% if show_agree_button %}
<br/>

View File

@@ -1,10 +1,16 @@
from django.conf.urls import include, url
import django_saml2_pro_auth.urls as saml_urls
from rugwebsite.views import home, gdpr, request_gdpr_delete
from rugwebsite.views import home, gdpr, gdpr_request_delete, gdpr_delete, gdpr_agree, gdpr_create_agree, \
gdpr_ask_agreement
urlpatterns = [
url(r'gdpr/$', gdpr, name='gdpr'),
url(r'gdpr-forget-me/$', request_gdpr_delete, name='gdpr-forget-me'),
url(r'gdpr-request-delete/$', gdpr_request_delete, name='gdpr-request-delete'),
url(r'gdpr-delete/(?P<email>[^/]+)/(?P<token>[a-zA-Z0-9]{32})/$', gdpr_delete, name='gdpr-delete'),
url(r'gdpr-agree/(?P<email>[^/]+)/(?P<token>[a-zA-Z0-9]{32})/$', gdpr_agree, name='gdpr-agree'),
url(r'gdpr-create-agree/$', gdpr_create_agree, name='gdpr-create-agree'),
url(r'gdpr-ask-for-agreement/$', gdpr_ask_agreement, name='gdpr-ask-for-agreement'),
url(r'', include(saml_urls, namespace='saml')),
url(r'$', home),
]

View File

@@ -1,8 +1,14 @@
import json
import hashlib
from django.contrib.auth import login
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.shortcuts import render
from django.shortcuts import render, redirect
from django.utils.module_loading import import_string
from django_saml2_pro_auth.auth import Backend
from rugwebsite.forms import RequestGDPRDelete
from rugwebsite.forms import RequestGDPRDelete, GDPRAgreeCreate
from rugwebsite.models import PendingGDPRAgree, GDPRAgreed, PendingGDPRDelete
from django.utils.crypto import get_random_string
@@ -21,8 +27,11 @@ def gdpr_ask_agreement(request):
if not request.user.is_superuser:
raise PermissionError()
for user in User.objects.filter(username__in=('p207263', 'p233780', 'p253591', 'p269380'),
is_active=True).all():
for user in User.objects.filter(
is_active=True).all():
if PendingGDPRAgree.objects.filter(user=user).exists() or GDPRAgreed.objects.filter(user=user).exists() \
or user.email is None or user.email == '':
continue
token = get_random_string(length=32)
pending = PendingGDPRAgree(user=user, token=token)
pending.save()
@@ -30,6 +39,35 @@ def gdpr_ask_agreement(request):
return render(request, 'rugwebsite/gdpr.html', {'show_agree_button': False, 'shownav': True})
def gdpr_create_agree(request):
if request.method == 'POST':
form = GDPRAgreeCreate(request.POST)
assert form.is_valid()
data = form.cleaned_data['data']
sha256 = hashlib.sha256()
sha256.update(data.encode('utf-8'))
assert request.session.get('samlPersoonsgegevensHash', None) == sha256.hexdigest(), "Persoonsgegevens have been tinkered with"
user = User()
user.username, user.first_name, user.last_name, user.email = json.loads(data)
user.is_active = True
user.save()
login(request, user, backend=request.session.get('samlBackend', 'django_saml2_pro_auth.auth.Backend'))
return render(request, 'rugwebsite/gdpr_agree_success.html', {'shownav': True})
else:
if request.user.is_authenticated():
data = json.dumps([request.user.username, request.user.first_name, request.user.last_name, request.user.email])
sha256 = hashlib.sha256()
sha256.update(data.encode('utf-8'))
request.user.delete()
request.session['samlPersoonsgegevensHash'] = sha256.hexdigest()
form = GDPRAgreeCreate(initial={'data': data})
return render(request, 'rugwebsite/gdpr.html', {'created': True, 'shownav': True, 'form': form})
return redirect('/')
def gdpr_request_delete(request):
if request.method == 'POST':
form = RequestGDPRDelete(request.POST)
@@ -77,4 +115,4 @@ def gdpr_agree(request, email, token):
else:
result['token_not_found'] = True
return render(request, 'rugwebsite/gdpr_agree_success.html', result, {'shownav': True})
return render(request, 'rugwebsite/gdpr_agree_success.html', result)