Compare commits
32 Commits
0b48db62c1
...
master
Author | SHA1 | Date | |
---|---|---|---|
b62c3104ca | |||
c049394e9e | |||
a150374397 | |||
50d5b9a7b9 | |||
ab56a48dd4 | |||
88fb40c54c | |||
bd8905313e | |||
88a3a6f086 | |||
69f3cda470 | |||
27486a4c6d | |||
4b4afb5521 | |||
99e2248626 | |||
fc7629edad | |||
05b97556c2 | |||
7ac1085ae2 | |||
1cd3795b87 | |||
637f4d2a55 | |||
42444c12dc | |||
163bda3e30 | |||
0e2901b62b | |||
fe93d23747 | |||
004c3a534f | |||
96991610c9 | |||
9a4e5ceafa | |||
df900898e8 | |||
c82c23aac4 | |||
36dcc0445b | |||
909d232480 | |||
de61de4be3 | |||
38fbabecaf | |||
37dde44830 | |||
300b971650 |
@@ -1 +1 @@
|
||||
__version__ = '0.1.30'
|
||||
__version__ = '0.1.37'
|
||||
|
Binary file not shown.
Binary file not shown.
@@ -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
11
rugwebsite/gdpr_urls.py
Normal 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'),
|
||||
]
|
@@ -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']),
|
||||
|
@@ -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&acs",
|
||||
"url": BASE_URL_SLASH + SAML_ROUTE_SLASH + "?provider=RuG&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": {{
|
||||
|
@@ -69,6 +69,8 @@ AUTHENTICATION_BACKENDS = [
|
||||
|
||||
SAML_ROUTE = 'sso/saml/'
|
||||
SAML_REDIRECT = '/'
|
||||
SAML_REDIRECT_CREATED = '/gdpr-just-created/'
|
||||
|
||||
SAML_USERS_MAP = []
|
||||
|
||||
SAML_PROVIDERS = []
|
||||
|
@@ -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>
|
||||
|
@@ -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.
|
||||
|
@@ -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.
|
||||
|
@@ -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/>
|
||||
|
@@ -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),
|
||||
]
|
||||
|
@@ -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'),
|
||||
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)
|
||||
|
Reference in New Issue
Block a user