diff --git a/VERSION b/VERSION
index 57121573..5eef0f10 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.10.1
+0.10.2
diff --git a/core/urls.py b/core/urls.py
index f646e450..27e95114 100644
--- a/core/urls.py
+++ b/core/urls.py
@@ -1,8 +1,10 @@
+from django.conf.urls import include
from django.conf.urls import url
from core import views as core_views
urlpatterns = [
- url(r'^$', core_views.home),
+ url(r'^$', core_views.home, name='home'),
+ url(r'^terms/', include('termsandconditions.urls')),
]
diff --git a/core/utils.py b/core/utils.py
index 8625351f..308549bd 100644
--- a/core/utils.py
+++ b/core/utils.py
@@ -1,22 +1,40 @@
-from HTMLParser import HTMLParser
-
-#class MLStripper(HTMLParser):
-# def __init__(self):
-# self.reset()
-# self.fed = []
-# def handle_data(self, d):
-# self.fed.append(d)
-# def get_data(self):
-# return ''.join(self.fed)
-#
-#def strip_tags(html):
-# s = MLStripper()
-# s.feed(html)
-# return s.get_data()
-
-
-class AttrDict(dict):
- __getattr__ = dict.__getitem__
-
- def __setattr__(self, key, value):
- self[key] = value
+from datetime import datetime
+
+from dateutil.relativedelta import relativedelta
+
+def ssn_is_formatted_correctly(ssn):
+ # We don't need any hard-core checksumming here, since we're only making
+ # sure that the data format is correct, so that we can safely retrieve
+ # parts of it through string manipulation.
+ return ssn.isdigit() and len(ssn) == 10
+
+def calculate_age_from_ssn(ssn):
+ if not ssn_is_formatted_correctly(ssn):
+ raise AttributeError('SSN must be numeric and exactly 10 digits long')
+
+ # Determine year.
+ century_num = ssn[9:]
+ if century_num == '9':
+ century = 1900
+ elif century_num == '0':
+ century = 2000
+ else:
+ raise AttributeError('%s is not a known number for any century' % century_num)
+ year = century + int(ssn[4:6])
+
+ # Determine month and day
+ month = int(ssn[2:4])
+ day = int(ssn[0:2])
+
+ # Calculate the differences between birthdate and today.
+ birthdate = datetime(year, month, day)
+ today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
+ age = relativedelta(today, birthdate).years
+
+ return age
+
+def is_ssn_human_or_institution(ssn):
+ if not ssn_is_formatted_correctly(ssn):
+ raise AttributeError('SSN must be numeric and exactly 10 digits long')
+
+ return 'institution' if int(ssn[0:2]) > 31 else 'human'
diff --git a/core/views.py b/core/views.py
index 1c9e7936..334f16dc 100644
--- a/core/views.py
+++ b/core/views.py
@@ -1,5 +1,6 @@
from datetime import datetime, timedelta
+from xml.etree.ElementTree import ParseError
import os.path
import json
@@ -11,6 +12,7 @@
from urlparse import parse_qs
# SSO done
+from django.contrib.auth import logout
from django.core.urlresolvers import reverse
from django.shortcuts import render
from django.shortcuts import redirect, get_object_or_404
@@ -33,6 +35,8 @@
from core.models import UserProfile
from core.forms import UserProfileForm
from core.saml import authenticate, SamlException
+from core.utils import calculate_age_from_ssn
+from core.utils import is_ssn_human_or_institution
from election.models import Election
from issue.forms import DocumentForm
from issue.models import Document
@@ -231,6 +235,29 @@ def verify(request):
except SamlException as e:
ctx = {'e': e}
return render(request, 'registration/saml_error.html', ctx)
+ except ParseError:
+ logout(request)
+ return redirect(reverse('auth_login'))
+
+ # Make sure that the user is, in fact, human.
+ if is_ssn_human_or_institution(auth['ssn']) != 'human':
+ ctx = {
+ 'ssn': auth['ssn'],
+ 'name': auth['name'].encode('utf8'),
+ }
+ return render(request, 'registration/verification_invalid_entity.html', ctx)
+
+
+ # Make sure that user has reached the minimum required age, if applicable.
+ if hasattr(settings, 'AGE_LIMIT') and settings.AGE_LIMIT > 0:
+ age = calculate_age_from_ssn(auth['ssn'])
+ if age < settings.AGE_LIMIT:
+ logout(request)
+ ctx = {
+ 'age': age,
+ 'age_limit': settings.AGE_LIMIT,
+ }
+ return render(request, 'registration/verification_age_limit.html', ctx)
if UserProfile.objects.filter(verified_ssn=auth['ssn']).exists():
taken_user = UserProfile.objects.select_related('user').get(verified_ssn=auth['ssn']).user
@@ -239,7 +266,7 @@ def verify(request):
'taken_user': taken_user,
}
- auth_logout(request)
+ logout(request)
return render(request, 'registration/verification_duplicate.html', ctx)
diff --git a/requirements.txt b/requirements.txt
index cdcaf2f5..7614fa8b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -20,6 +20,7 @@ django-datetime-widget==0.9.3
django-debug-toolbar==1.9.1
django-prosemirror==0.0.12
django-registration-redux==1.10
+django-termsandconditions==1.2.8
eight==0.4.2
enum34==1.1.6
et-xmlfile==1.0.1
@@ -61,6 +62,7 @@ pyflakes==1.6.0
Pygments==2.2.0
PyMySQL==0.8.1
pyOpenSSL==18.0.0
+python-dateutil==2.7.3
python-memcached==1.59
python-vote-full==1.0
pytz==2018.4
diff --git a/wasa2il/default_settings.py b/wasa2il/default_settings.py
index ba77b533..981bb178 100644
--- a/wasa2il/default_settings.py
+++ b/wasa2il/default_settings.py
@@ -29,6 +29,9 @@
ALLOW_LEAVE_POLITY = False
+# Age limit for participation. (Currently only works with SAML.)
+AGE_LIMIT = 16
+
DATE_FORMAT = 'd/m/Y'
DATETIME_FORMAT = 'd/m/Y H:i:s'
DATETIME_FORMAT_DJANGO_WIDGET = 'dd/mm/yyyy hh:ii' # django-datetime-widget
diff --git a/wasa2il/local_settings.py-example b/wasa2il/local_settings.py-example
index 0256fd0b..b6c88ce0 100644
--- a/wasa2il/local_settings.py-example
+++ b/wasa2il/local_settings.py-example
@@ -23,6 +23,9 @@ TIME_ZONE = 'Iceland'
ALLOW_LEAVE_POLITY = False
+# Age limit for participation. (Currently only works with SAML.)
+AGE_LIMIT = 16
+
DATE_FORMAT = 'd/m/Y'
DATETIME_FORMAT = 'd/m/Y H:i:s'
DATETIME_FORMAT_DJANGO_WIDGET = 'dd/mm/yyyy hh:ii' # django-datetime-widget
diff --git a/wasa2il/locale/is/LC_MESSAGES/django.po b/wasa2il/locale/is/LC_MESSAGES/django.po
index e85601dc..a33e1c56 100644
--- a/wasa2il/locale/is/LC_MESSAGES/django.po
+++ b/wasa2il/locale/is/LC_MESSAGES/django.po
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-05-19 16:26+0000\n"
+"POT-Creation-Date: 2018-05-20 21:02+0000\n"
"Language-Team: Icelandic\n"
"Language: is\n"
"MIME-Version: 1.0\n"
@@ -284,6 +284,7 @@ msgid "Deprecated"
msgstr "Úrelt"
#: issue/views.py:79
+#: wasa2il/templates/termsandconditions/tc_accept_terms.html:20
msgid "version"
msgstr "útgáfa"
@@ -585,7 +586,9 @@ msgid "My settings"
msgstr "Stillingar"
#: wasa2il/templates/base.html:83
+#: wasa2il/templates/registration/verification_invalid_entity.html:17
#: wasa2il/templates/registration/verification_needed.html:12
+#: wasa2il/templates/termsandconditions/tc_accept_terms.html:49
msgid "Logout"
msgstr "Útskrá"
@@ -626,7 +629,8 @@ msgstr "Við notum vafrakökur (e. cookie) til auðkenningar."
#: wasa2il/templates/cookie_dialog.html:33
msgid "We do not use them to track you."
-msgstr "Við notum þær ekki til þess að fylgjast með vefnotkun þinni."
+msgstr ""
+"Við notum þær ekki til þess að fylgjast með vefnotkun þinni."
#: wasa2il/templates/cookie_dialog.html:38
msgid "I accept cookies being used for authentication purposes."
@@ -2023,6 +2027,7 @@ msgstr "Þú getur núna reynt að innskrá þig!"
#: wasa2il/templates/registration/activation_complete.html:11
#: wasa2il/templates/registration/login.html:28
+#: wasa2il/templates/registration/verification_duplicate.html:14
msgid "Login"
msgstr "Innskrá"
@@ -2284,6 +2289,32 @@ msgstr ""
"Vinsamlegast reynið aftur og ef villan kemur aftur, hafið samband við "
"umsjónarmann."
+#: wasa2il/templates/registration/verification_age_limit.html:10
+msgid "Age below age limit"
+msgstr "Lágmarksaldri ekki náð"
+
+#: wasa2il/templates/registration/verification_age_limit.html:12
+#, python-format
+msgid ""
+"We are sorry to inform you that you have not yet reached the minimum "
+"required age of %(age_limit)s years. According to our information, you are "
+"%(age)s years old."
+msgstr ""
+"Okkur þykir leitt að tilkynna þér að þú hefur ekki enn náð lágmarksaldri "
+"fyrir aðgang, sem er %(age_limit)s ár. Samkvæmt okkar gögnum ertu %(age)s "
+"ára."
+
+#: wasa2il/templates/registration/verification_age_limit.html:14
+msgid ""
+"We will be very happy to provide you with access once you reach the minimum "
+"required age."
+msgstr ""
+"Við veitum þér aðgang með mikilli gleði þegar þú hefur náð tilsettum aldri."
+
+#: wasa2il/templates/registration/verification_age_limit.html:16
+msgid "Front page"
+msgstr "Forsíða"
+
#: wasa2il/templates/registration/verification_duplicate.html:6
msgid "Multiple accounts detected"
msgstr "Fleiri en einn reikningur til"
@@ -2312,6 +2343,24 @@ msgstr "Notandanafn"
msgid "Email"
msgstr "Netfang"
+#: wasa2il/templates/registration/verification_invalid_entity.html:10
+msgid "Invalid entity"
+msgstr "Ógildur aðili"
+
+#: wasa2il/templates/registration/verification_invalid_entity.html:12
+#, python-format
+msgid "The entity %(name)s with the SSN %(ssn)s is an invalid entity."
+msgstr "Aðilinn %(name)s með kennitöluna %(ssn)s er ógildur aðili."
+
+#: wasa2il/templates/registration/verification_invalid_entity.html:14
+msgid "Please try using a valid entity for verifying your account."
+msgstr ""
+"Vinsamlegast reyndu aftur með gildum aðila til að staðfesta aðganginn þinn."
+
+#: wasa2il/templates/registration/verification_invalid_entity.html:16
+msgid "Try again"
+msgstr "Reyna aftur"
+
#: wasa2il/templates/registration/verification_needed.html:6
msgid "Verification needed"
msgstr "Staðfestingar er þörf"
@@ -2503,6 +2552,29 @@ msgstr ""
msgid "User currently isn't volunteering for any tasks."
msgstr ""
+#: wasa2il/templates/termsandconditions/tc_accept_terms.html:19
+msgid "Terms and Conditions"
+msgstr "Notkunarskilmálar"
+
+#: wasa2il/templates/termsandconditions/tc_accept_terms.html:23
+msgid ""
+"In order to use this software, you must accept the following terms and "
+"conditions."
+msgstr ""
+"Til að nota þennan hugbúnað verður þú að samþykkja eftirfarandi skilmála."
+
+#: wasa2il/templates/termsandconditions/tc_accept_terms.html:26
+msgid "Information"
+msgstr "Upplýsingar"
+
+#: wasa2il/templates/termsandconditions/tc_accept_terms.html:44
+msgid "Accept all"
+msgstr "Samþykkja alla"
+
+#: wasa2il/templates/termsandconditions/tc_accept_terms.html:46
+msgid "Accept"
+msgstr "Samþykkja"
+
#: wasa2il/templates/topic/_topic_list_table.html:19
msgid "There are no topics in this polity!"
msgstr "Það eru engir málaflokkar á þessu þingi!"
@@ -2619,9 +2691,6 @@ msgstr ""
#~ msgid "Issues in polity"
#~ msgstr "Mál í þingi"
-#~ msgid "Toggle information"
-#~ msgstr "Sýna/Fela upplýsingar"
-
#~ msgid "Show all agreements"
#~ msgstr "Sýna allar samþykktir"
diff --git a/wasa2il/settings.py b/wasa2il/settings.py
index d96e2187..ebbfedbb 100644
--- a/wasa2il/settings.py
+++ b/wasa2il/settings.py
@@ -114,6 +114,7 @@
'core.middleware.UserSettingsMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
'core.middleware.GlobalsMiddleware',
+ 'termsandconditions.middleware.TermsAndConditionsRedirectMiddleware',
)
try:
MIDDLEWARE_CLASSES += LOCAL_MIDDLEWARE_CLASSES
@@ -165,6 +166,7 @@
'datetimewidget',
'crispy_forms',
'prosemirror',
+ 'termsandconditions',
'core',
'polity',
@@ -225,6 +227,15 @@
}
}
+TERMS_EXCLUDE_URL_PREFIX_LIST = (
+ '/admin/',
+ '/help/',
+ '/accounts/register/',
+ '/accounts/login/',
+ '/accounts/logout/',
+ '/accounts/verify/',
+)
+
AUTH_PROFILE_MODULE = "core.UserProfile"
ACCOUNT_ACTIVATION_DAYS = 7
LOGIN_REDIRECT_URL = "/"
diff --git a/wasa2il/templates/registration/verification_age_limit.html b/wasa2il/templates/registration/verification_age_limit.html
new file mode 100644
index 00000000..ac8baff0
--- /dev/null
+++ b/wasa2il/templates/registration/verification_age_limit.html
@@ -0,0 +1,23 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block content %}
+
+{% trans 'Age below age limit' %}
+
+ {% blocktrans %}We are sorry to inform you that you have not yet reached the minimum required age of {{ age_limit }} years. According to our information, you are {{ age }} years old.{% endblocktrans %}
+
+ {% trans 'We will be very happy to provide you with access once you reach the minimum required age.' %}
+
+ {% trans 'Front page' %}
+
+
{% trans 'In order to use this software, you must accept the following terms and conditions.' %}
+ + {% if terms.info %} +{{ terms.info|markdown }}
+