Commit 219ab1a8 authored by Giorgos Kazelidis's avatar Giorgos Kazelidis

- Corrected some minor display issues in login template

- Created the DB schema/models and added Greek support to the initiated DB
- Created a script to populate DB with test data
- Implemented the authentication, login and logout back-ends (modified built-in auth, backends and middleware modules) as well as the relative front-ends (user home, admin home and logout templates)
- Implemented some basic validation checks and error handling for the login form
- Created custom HTTP error (400, 403, 404, 500) templates
- Added new instructions to README.md
parent 6e047b62
...@@ -25,7 +25,9 @@ ...@@ -25,7 +25,9 @@
git config --global user.name "YOUR_NAME" git config --global user.name "YOUR_NAME"
git config --global user.email "YOUR_EMAIL" git config --global user.email "YOUR_EMAIL"
(D) create a local instance of the remote repository (userbase): (D) create a local instance of the remote repository (userbase) [sources:
      -- https://git-scm.com/docs/git-clone
      ]:
      (1) navigate to Desktop:       (1) navigate to Desktop:
cd ~/Desktop cd ~/Desktop
...@@ -41,7 +43,12 @@ ...@@ -41,7 +43,12 @@
      -- end of section http://docs.python-guide.org/en/latest/dev/virtualenvs/#lower-level-virtualenv       -- end of section http://docs.python-guide.org/en/latest/dev/virtualenvs/#lower-level-virtualenv
      -- https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-16-04       -- https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-16-04
      -- "Create a MySQL Database" section of http://www.marinamele.com/taskbuster-django-tutorial/install-and-configure-mysql-for-django       -- "Create a MySQL Database" section of http://www.marinamele.com/taskbuster-django-tutorial/install-and-configure-mysql-for-django
      -- https://gist.github.com/hofmannsven/9164408
      -- https://www.tutorialspoint.com/django/django_admin_interface.htm       -- https://www.tutorialspoint.com/django/django_admin_interface.htm
      -- https://docs.djangoproject.com/en/2.0/ref/django-admin/
      -- https://docs.python.org/3/tutorial/interpreter.html
      -- https://stackoverflow.com/questions/436198/what-is-an-alternative-to-execfile-in-python-3
      -- https://docs.python.org/3.6/tutorial/inputoutput.html#reading-and-writing-files
      ]:       ]:
      (1) specify the full file path (/usr/bin/python3) to the installed Python version:       (1) specify the full file path (/usr/bin/python3) to the installed Python version:
...@@ -68,15 +75,32 @@ ...@@ -68,15 +75,32 @@
sudo apt-get install mysql-server sudo apt-get install mysql-server
mysql_secure_installation mysql_secure_installation
       (7) use the password that was set for the root user during MySQL server installation as the 'PASSWORD' value of the 'default' dictionary of the DATABASES dictionary in **~/Desktop/userbase/myprj/myprj/settings.py**        (7) use the password that was set for the root user during MySQL server installation as the 'PASSWORD' value of the 'default' dictionary of the DATABASES dictionary in **~/Desktop/userbase/myprj/myprj/settings.py**
       (8) if you have not created the project database (usermergeDB) yet, open MySQL console with root privileges (you should provide the password that was set for the root user during MySQL server installation), create the project database and exit MySQL console:        (8) open MySQL console with root privileges:
python manage.py dbshell
           ALTERNATIVELY, you could run the following command by providing the password that was set for the root user during MySQL server installation:
mysql -u root -p
       (9) if the project database (usermergeDB) has already been created (`SHOW DATABASES;` to check), delete it:
DROP DATABASE usermergeDB;
       (10) create the project database:
mysql -u root -p (ALTERNATIVELY: python manage.py dbshell)
CREATE DATABASE usermergeDB; CREATE DATABASE usermergeDB;
       (11) exit MySQL console:
EXIT; EXIT;
       (9) initiate the database:        (12) initiate the database:
python manage.py migrate python manage.py migrate
       (10) deactivate the virtual environment:        (13) open Python interpreter in interactive mode:
python manage.py shell
       (14) populate the database with test data, i.e. execute populateDB.py script, by typing the following line and pressing `Enter` TWICE:
with open('./populateDB.py') as popDB: exec(popDB.read())
       (15) close Python interpreter by pressing `Ctrl-D`
       (16) deactivate the virtual environment:
deactivate deactivate
...@@ -91,7 +115,7 @@ ...@@ -91,7 +115,7 @@
(C\) start the Django development server (at http://127.0.0.1:8000/): (C\) start the Django development server (at http://127.0.0.1:8000/):
python manage.py runserver python manage.py runserver
(D) navigate to http://127.0.0.1:8000/usermerge/login/ via web browser to run the project (D) navigate to http://127.0.0.1:8000/usermerge/login/default/ via web browser to run the project
## STOPPING THE PROJECT (AND EXITING THE VIRTUAL ENVIRONMENT) ## STOPPING THE PROJECT (AND EXITING THE VIRTUAL ENVIRONMENT)
(A) stop the Django development server (running at http://127.0.0.1:8000/): (A) stop the Django development server (running at http://127.0.0.1:8000/):
......
...@@ -45,7 +45,7 @@ MIDDLEWARE = [ ...@@ -45,7 +45,7 @@ MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'usermerge.middleware.AuthenticationMiddleware', # instead of default 'django.contrib.auth.middleware.AuthenticationMiddleware'
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
] ]
...@@ -70,6 +70,10 @@ TEMPLATES = [ ...@@ -70,6 +70,10 @@ TEMPLATES = [
WSGI_APPLICATION = 'myprj.wsgi.application' WSGI_APPLICATION = 'myprj.wsgi.application'
AUTHENTICATION_BACKENDS = ['usermerge.backends.UserBackend']
LOGIN_URL = '/usermerge/login/default/'
# Database # Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases # https://docs.djangoproject.com/en/2.0/ref/settings/#databases
...@@ -84,8 +88,12 @@ DATABASES = { ...@@ -84,8 +88,12 @@ DATABASES = {
'HOST': '', 'HOST': '',
'PORT': '', 'PORT': '',
'OPTIONS': { 'OPTIONS': {
'init_command': "SET default_storage_engine=INNODB;" \ 'init_command': 'SET default_storage_engine = INNODB;' \
"SET sql_mode='STRICT_TRANS_TABLES'", "SET sql_mode = 'STRICT_TRANS_TABLES';" \
# greek language support
# https://stackoverflow.com/questions/37307146/difference-between-utf8mb4-unicode-ci-and-utf8mb4-unicode-520-ci-collations-in-m
# https://stackoverflow.com/questions/43644218/why-is-table-charset-set-to-utf8mb4-and-collation-to-utf8mb4-unicode-520-ci?rq=1
'ALTER DATABASE usermergeDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;',
}, },
} }
} }
......
"""
Populate usermergeDB (see models.py) with test data to check the correctness of the usermerge application features.
"""
from django.contrib.auth.hashers import make_password
from usermerge.models import Admin, Platform, Registry, User
# Populate Admin table with test admins.
admin_hashed_password = make_password('sesame')
admin1 = Admin.objects.create(first_name = 'Νικόλαος', last_name = 'Παπασπύρου', email = 'nikolaos@softlab.ntua.gr',
username = 'nikolaos', password = admin_hashed_password)
admin2 = Admin.objects.create(first_name = 'Ευστάθιος', last_name = 'Ζάχος', email = 'eustathios@corelab.ntua.gr',
username = 'eustathios', password = admin_hashed_password)
# Populate Platform table with test platforms.
ece_ntua = Platform.objects.create(name = 'ECE-NTUA')
novice = Platform.objects.create(name = 'Novice')
grader = Platform.objects.create(name = 'Grader')
moodle = Platform.objects.create(name = 'Moodle')
plgrader = Platform.objects.create(name = 'PLgrader')
# Populate User table with test users.
user1 = User.objects.create(first_name = 'Γεώργιος', last_name = 'Καζελίδης', ece_id = '03199999',
email = 'gkazelid@undergraduate.ece.ntua.gr')
user2 = User.objects.create()
# Populate Registry table with test registries.
user_hashed_password = make_password('password')
registry1 = Registry.objects.create(user = user1, platform = novice, username = 'pi99b999', password = user_hashed_password)
registry2 = Registry.objects.create(user = user2, platform = moodle, username = 'moodler', password = user_hashed_password)
"""
Modified version of django.contrib.auth module (https://docs.djangoproject.com/en/2.0/_modules/django/contrib/auth/).
"""
from django.apps import apps as django_apps
from django.conf import settings
from django.contrib.auth import _get_backends, load_backend
from django.contrib.auth.signals import user_logged_in
from django.middleware.csrf import rotate_token
from django.utils.crypto import constant_time_compare
BACKEND_SESSION_KEY = '_auth_user_backend'
CLASS_SESSION_KEY = '_auth_user_class'
HASH_SESSION_KEY = '_auth_user_hash'
SESSION_KEY = '_auth_user_id'
def get_user_model(user_class):
"""
Return the user model (User/Admin from usermerge.models - see models.py) that is active in the session.
"""
# user_class is either 'usermerge.User' or 'usermerge.Admin' (the format should necessarily be 'app_label.model_name').
return django_apps.get_model(user_class, require_ready = False)
def _get_user_session_key(request):
# This value in the session is always serialized to a string, so we need
# to convert it back to Python whenever we access it.
return get_user_model(request.session[CLASS_SESSION_KEY])._meta.pk.to_python(request.session[SESSION_KEY])
def login(request, user, backend = None):
"""
Persist a user id and a backend in the request. This way a user doesn't
have to reauthenticate on every request. Note that data set during
the anonymous session is retained when the user logs in.
"""
session_auth_hash = ''
if user is None:
user = request.user
if hasattr(user, 'get_session_auth_hash'):
session_auth_hash = user.get_session_auth_hash()
if SESSION_KEY in request.session:
if _get_user_session_key(request) != user.pk or (
session_auth_hash and
not constant_time_compare(request.session.get(HASH_SESSION_KEY, ''), session_auth_hash)):
# To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.
request.session.flush()
else:
request.session.cycle_key()
try:
backend = backend or user.backend
except AttributeError:
backends = _get_backends(return_tuples = True)
if len(backends) == 1:
_, backend = backends[0]
else:
raise ValueError(
'You have multiple authentication backends configured and '
'therefore must provide the `backend` argument or set the '
'`backend` attribute on the user.'
)
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
# The value of CLASS_SESSION_KEY is needed to clarify if the authenticated user
# is a User ('usermerge.User') or an Admin ('usermerge.Admin').
request.session[CLASS_SESSION_KEY] = str(user.__class__).replace('.models', '').split("'")[1]
request.session[BACKEND_SESSION_KEY] = backend
request.session[HASH_SESSION_KEY] = session_auth_hash
if hasattr(request, 'user'):
request.user = user
rotate_token(request)
user_logged_in.send(sender = user.__class__, request = request, user = user)
def get_user(request):
"""
Return the user model instance associated with the given request session.
If no user is retrieved, return an instance of `AnonymousUser`.
"""
from django.contrib.auth.models import AnonymousUser
user = None
try:
user_id = _get_user_session_key(request)
backend_path = request.session[BACKEND_SESSION_KEY]
except KeyError:
pass
else:
if backend_path in settings.AUTHENTICATION_BACKENDS:
backend = load_backend(backend_path)
user = backend.get_user(request.session[CLASS_SESSION_KEY], user_id)
# Verify the session.
if hasattr(user, 'get_session_auth_hash'):
session_hash = request.session.get(HASH_SESSION_KEY)
session_hash_verified = session_hash and constant_time_compare(
session_hash,
user.get_session_auth_hash()
)
if not session_hash_verified:
request.session.flush()
user = None
return user or AnonymousUser()
from django.contrib.auth.hashers import check_password
from usermerge.models import Admin, Platform, Registry, User
# Create your backends here.
# https://docs.djangoproject.com/en/2.0/topics/auth/customizing/#other-authentication-sources
# For more information on Admin, Platform, Registry and User models, see models.py .
# For more information on the platform names that are provided in the drop-down list of the login form, see login.html and get_names_of_SoftLab_provided_platforms_from_DB() function of views.py .
# For more information on password management functions (e.g. check_password()), see https://docs.djangoproject.com/en/2.0/topics/auth/passwords/ .
class UserBackend:
def authenticate(self, request, platform, username, password):
"""
Authenticate the credentials POSTed via the login form.
If the selected platform is SLUB, the user should be authenticated as Admin with the happy path being:
* Try to find an Admin instance of usermergeDB that corresponds to the provided username.
* If a corresponding instance is found, check if the provided password matches the one of the instance.
* If the provided password matches the one of the instance, the user is authenticated successfully as Admin and
the instance is returned.
If the selected platform is not SLUB, the user should be authenticated as User with the happy path being:
* Try to find a Registry instance of usermergeDB that corresponds to the provided platform and username.
* If a corresponding instance is found, check if the provided password matches the one of the instance.
* If the provided password matches the one of the instance, the user is authenticated successfully as User and
the User instance that is associated with the corresponding Registry instance is returned.
If any of the checks (finding the corresponding instance and comparing the passwords) fails, the authentication
fails and an empty instance (None) is returned (if the first check fails, the second is never performed).
"""
if platform != 'SLUB':
# User authentication
platform_instance = Platform.objects.get(name = platform)
try:
registry = Registry.objects.get(platform = platform_instance, username = username)
except Registry.DoesNotExist:
return None
else:
if check_password(password, registry.password):
return registry.user
else:
return None
else:
# Admin authentication
try:
admin = Admin.objects.get(username = username)
except Admin.DoesNotExist:
return None
else:
if check_password(password, admin.password):
return admin
else:
return None
def get_user(self, user_class, user_id):
"""
Return the user instance associated with the given class/model (User/Admin) and id.
If no associated user instance is found, return an empty instance (None).
"""
if user_class == 'usermerge.User':
try:
return User.objects.get(pk = user_id)
except User.DoesNotExist:
return None
else:
try:
return Admin.objects.get(pk = user_id)
except Admin.DoesNotExist:
return None
import re
from django import forms
from usermerge.models import Platform
# Create your forms here.
# https://docs.djangoproject.com/en/2.0/ref/forms/
# The below-defined forms are based on the models defined in models.py .
# For more information on clean_<fieldname>() and clean() methods, see https://docs.djangoproject.com/en/2.0/ref/forms/validation/ .
# For more information on the platform names that are provided in the drop-down list of the login form, see login.html and get_names_of_SoftLab_provided_platforms_from_DB() function of views.py .
class LoginForm(forms.Form):
platform = forms.CharField(error_messages = {'required': 'Το δηλωθέν όνομα πλατφόρμας δεν ανταποκρίνεται σε καμία από τις '
'υποστηριζόμενες πλατφόρμες της βάσης!'})
username = forms.CharField(max_length = 50,
error_messages = {'required': 'Το όνομα χρήστη πρέπει απαραίτητα να συμπληρωθεί!',
'max_length': 'Το όνομα χρήστη δεν πρέπει να υπερβαίνει τους 50 χαρακτήρες!'})
password = forms.CharField(max_length = 50,
error_messages = {'required': 'Ο κωδικός πρόσβασης πρέπει απαραίτητα να συμπληρωθεί!',
'max_length': 'Ο κωδικός πρόσβασης δεν πρέπει να υπερβαίνει τους 50 χαρακτήρες!'})
def clean_platform(self):
platform = self.cleaned_data['platform']
# Ensure that the platform (name) is selected among the provided ones in the drop-down list of the login form.
# If it is not (e.g. it is misedited via JavaScript), raise a ValidationError that will be printed under the form.
if platform == 'SLUB' or (platform != 'ECE-NTUA' and platform in Platform.objects.values_list('name', flat = True)):
return platform
else:
raise forms.ValidationError('Το δηλωθέν όνομα πλατφόρμας δεν ανταποκρίνεται σε καμία '
'από τις υποστηριζόμενες πλατφόρμες της βάσης!')
def clean(self):
cleaned_data = super().clean()
# If platform and username have survived the initial individual field checks (by the time the form’s clean() method is called,
# all the individual field clean methods will have been run, so cleaned_data will be populated with any data that has survived
# so far) and platform refers either to Novice or to Grader, ensure that the username format is piYYbRRR (if it is not, raise a
# ValidationError that will be printed under the login form), where YY refers to year and RRR refers to username serial number.
if set(('platform', 'username')).issubset(cleaned_data):
platform = cleaned_data['platform']
username = cleaned_data['username']
if platform == 'Novice' or platform == 'Grader':
if not re.match('pi[0-9]{2}b[0-9]{3}', username):
raise forms.ValidationError('Το όνομα χρήστη στις πλατφόρμες Novice και Grader πρέπει να είναι '
'της μορφής piYYbRRR (ΥΥ: έτος, RRR: σειριακός αριθμός ονόματος)!')
"""
Modified version of django.contrib.auth.middleware module (https://github.com/django/django/blob/master/django/contrib/auth/middleware.py).
"""
from django.conf import settings
from django.utils.deprecation import MiddlewareMixin
from django.utils.functional import SimpleLazyObject
from usermerge import auth
# Create your middleware here.
# https://docs.djangoproject.com/en/2.0/topics/http/middleware/
def get_user(request):
if not hasattr(request, '_cached_user'):
request._cached_user = auth.get_user(request)
return request._cached_user
class AuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
assert hasattr(request, 'session'), (
"The Django authentication middleware requires session middleware "
"to be installed. Edit your MIDDLEWARE%s setting to insert "
"'django.contrib.sessions.middleware.SessionMiddleware' before "
"'usermerge.middleware.AuthenticationMiddleware'."
) % ("_CLASSES" if settings.MIDDLEWARE is None else "")
request.user = SimpleLazyObject(lambda: get_user(request))
# Generated by Django 2.0.4 on 2018-06-28 11:10
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Admin',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=100)),
('last_name', models.CharField(max_length=100)),
('email', models.EmailField(max_length=254, unique=True)),
('username', models.CharField(max_length=50, unique=True)),
('password', models.CharField(max_length=100)),
('last_login', models.DateTimeField(null=True)),
],
),
migrations.CreateModel(
name='Platform',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, unique=True)),
],
),
migrations.CreateModel(
name='Registry',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('username', models.CharField(max_length=50)),
('password', models.CharField(max_length=100)),
('platform', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='usermerge.Platform')),
],
),
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=100)),
('last_name', models.CharField(max_length=100)),
('ece_id', models.CharField(max_length=8, null=True, unique=True)),
('email', models.EmailField(max_length=254, null=True, unique=True)),
('last_login', models.DateTimeField(null=True)),
],
),
migrations.AddField(
model_name='registry',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='usermerge.User'),
),
migrations.AlterUniqueTogether(
name='registry',
unique_together={('user', 'platform'), ('platform', 'username')},
),
]
from django.db import models from django.db import models
from django.utils.crypto import salted_hmac
# Create your models here. # Create your models here.
# https://docs.djangoproject.com/en/2.0/ref/models/
# Regarding the fields whose null attribute/option is set to True (WARNING: Any unique fields whose null attribute/option is undeclared, i.e. False by default, should NEVER be allowed to be empty): The empty DB value for DateTimeFields is NULL (interpreted as None in Python), thus designating the absence of value. The empty DB value for CharFields and EmailFields is the empty/zero-length string (''), which is a valid string value. For unique CharFields and EmailFields, such as ece_id and email of the User model, whose value may NOT always be declared when inserting/updating the respective DB entries/records, this could lead in integrity errors ("Duplicate entry '' for key 'ece_id'/'email'"). To avoid such errors, NULL should be used as the empty DB value of these fields (the empty string should NOT be used in this case). For more information, see https://docs.djangoproject.com/en/2.0/ref/models/fields/ .
# first_name and last_name fields can contain greek characters. The length of each greek character is 2 bytes, while the length of each english/punctuation character is 1 byte.
# Django creates hashed passwords using the PBKDF2 algorithm with a SHA256 hash by default (https://docs.djangoproject.com/en/2.0/topics/auth/passwords/). The length of each hashed password is 78 bytes.
# The ECE-ID format is 031YYRRR (YY: year, RRR: registration number).
# The user_logged_in signal (see login() function in auth.py) triggers the update of the last_login fields, so the latter are necessary.
# Original source code for get_session_auth_hash() methods and is_authenticated attributes can be found in https://github.com/django/django/blob/master/django/contrib/auth/base_user.py (for more information, see https://docs.djangoproject.com/en/2.0/topics/auth/customizing/). get_session_auth_hash() methods are needed in login() and get_user() functions of auth.py, while is_authenticated attributes are needed for login_required() decorator of views.py .
class User(models.Model):
first_name = models.CharField(max_length = 100)
last_name = models.CharField(max_length = 100)
ece_id = models.CharField(max_length = 8, unique = True, null = True)
email = models.EmailField(unique = True, null = True)
last_login = models.DateTimeField(null = True)
def get_session_auth_hash(self):
"""
Return an HMAC of the email field.
"""
key_salt = 'usermerge.models.User.get_session_auth_hash'
return salted_hmac(key_salt, self.email).hexdigest()
@property
def is_authenticated(self):
"""
Always return True. This is a way to tell if the user has been
authenticated in views.
"""
return True
class Platform(models.Model):
name = models.CharField(max_length = 50, unique = True)
class Registry(models.Model):
user = models.ForeignKey('User', on_delete = models.PROTECT)
platform = models.ForeignKey('Platform', on_delete = models.PROTECT)
username = models.CharField(max_length = 50)
password = models.CharField(max_length = 100)
class Meta:
unique_together = (('user', 'platform',), ('platform', 'username',),)
class Admin(models.Model):
first_name = models.CharField(max_length = 100)
last_name = models.CharField(max_length = 100)
email = models.EmailField(unique = True)
username = models.CharField(max_length = 50, unique = True)
password = models.CharField(max_length = 100)
last_login = models.DateTimeField(null = True)
def get_session_auth_hash(self):
"""
Return an HMAC of the email field.
"""
key_salt = 'usermerge.models.Admin.get_session_auth_hash'
return salted_hmac(key_salt, self.email).hexdigest()
@property
def is_authenticated(self):
"""
Always return True. This is a way to tell if the user has been
authenticated in views.
"""
return True
{# https://docs.djangoproject.com/en/2.0/ref/views/#the-400-bad-request-view #}
{# https://docs.djangoproject.com/en/2.0/_modules/django/views/defaults/ #}
{% extends "base.html" %}
{% block title %}
SLUB - Σφάλμα 400 (Bad Request)
{% endblock %}
{% block content %}
<h4>Σφάλμα 400 (Bad Request)</h4>
{% endblock %}
{# https://docs.djangoproject.com/en/2.0/ref/views/#the-403-http-forbidden-view #}
{# https://docs.djangoproject.com/en/2.0/_modules/django/views/defaults/ #}
{% extends "base.html" %}
{% block title %}
SLUB - Σφάλμα 403 (Forbidden)
{% endblock %}
{% block content %}
<h4>Σφάλμα 403 (Forbidden)</h4>
{% endblock %}
{# https://docs.djangoproject.com/en/2.0/ref/views/#the-404-page-not-found-view #}
{# https://docs.djangoproject.com/en/2.0/_modules/django/views/defaults/ #}
{% extends "base.html" %}
{% block title %}
SLUB - Σφάλμα 404 (Not Found)
{% endblock %}
{% block content %}
<h4>Σφάλμα 404 (Not Found)</h4>
<p>Η ζητηθείσα ιστοσελίδα "{{ request_path }}" δεν βρέθηκε στον παρόντα διακομιστή! :(</p>
{% endblock %}
{# https://docs.djangoproject.com/en/2.0/ref/views/#the-500-server-error-view #}
{# https://docs.djangoproject.com/en/2.0/_modules/django/views/defaults/ #}
{% extends "base.html" %}
{% block title %}
SLUB - Σφάλμα 500 (Server Error)
{% endblock %}
{% block content %}
<h4>Σφάλμα 500 (Server Error)</h4>
{% endblock %}
{% extends "base.html" %}
{% block title %}
SLUB - Αρχική Σελίδα Διαχειριστή
{% endblock %}
{% block content %}
<div class="bar" id="in-session-bar" style="padding-top:10px; padding-bottom:10px;">Έχετε εισέλθει επιτυχώς! :)</div>
<h4>Αρχική Σελίδα Διαχειριστή</h4>
<p><a href="{% url 'logout' %}">Έξοδος</a></p>
{% endblock %}
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
{% endblock %} {% endblock %}
</title> </title>
<!-- https://docs.djangoproject.com/en/2.0/howto/static-files/ --> {# https://docs.djangoproject.com/en/2.0/howto/static-files/ #}
{% load static %} {% load static %}
<link rel="icon" href="{% static 'usermerge/favicon.png' %}" type="image/png"/> <link rel="icon" href="{% static 'usermerge/favicon.png' %}" type="image/png"/>
...@@ -21,11 +21,17 @@ ...@@ -21,11 +21,17 @@
text-align: center; text-align: center;
text-decoration: underline; text-decoration: underline;
} }
.footer, .header { p {
text-align: center;
}
table {
margin: 0px auto; {# https://stackoverflow.com/questions/14073188/center-align-a-html-table #}
}
.bar, .footer, .header {
text-align: center; text-align: center;
width: 100%; width: 100%;
} }
#ece-ntua-header, #footer { #ece-ntua-header, #footer, #in-session-bar {
background-color: #82B7EF; background-color: #82B7EF;
} }
#slub-header { #slub-header {
...@@ -36,9 +42,9 @@ ...@@ -36,9 +42,9 @@
<body> <body>
<div class="header" id="ece-ntua-header"> <div class="header" id="ece-ntua-header">
<table align="center" width="1000px"> <table>
<tr> <tr>
<td style="width:235px;"> <td style="width:240px;">
<img src="https://courses.softlab.ntua.gr/Images/Various/pyrforos.svg" alt="Προμηθέας Πυρφόρος" style="height:120px; width:120px;"/> <img src="https://courses.softlab.ntua.gr/Images/Various/pyrforos.svg" alt="Προμηθέας Πυρφόρος" style="height:120px; width:120px;"/>
</td> </td>
<td> <td>
...@@ -52,15 +58,22 @@ ...@@ -52,15 +58,22 @@
</div> </div>
<div class="header" id="slub-header"> <div class="header" id="slub-header">
<table align="center" width="1000px"> <table>
<tr> <tr>
<td style="width:235px;"> <td style="padding-left:10px; width:240px;">
<p><strong><span style="color:red;">S</span>oft<span style="color:red;">L</span>ab <span style="color:red;">U</span>ser<span style="color:red;">B</span>ase</strong></p> <p>
<strong>
<span style="color:red;">S</span>oft<span style="color:red;">L</span>ab
<span style="color:red;">U</span>ser<span style="color:red;">B</span>ase
</strong>
</p>
</td> </td>
<td> <td>
<p> <p>
Το <strong><span style="color:red;">SLUB</span></strong> είναι το σύστημα διαχείρισης και συσχέτισης διαπιστευτηρίων για τους χρήστες των πλατφορμών του SoftLab. Οι πλατφόρμες που διατίθενται μέσω του SoftLab είναι Το <strong><span style="color:red;">SLUB</span></strong> είναι το σύστημα διαχείρισης και συσχέτισης <br/>
ο <a href="novice.softlab.ntua.gr" target="_blank">Novice</a>, διαπιστευτηρίων για τους χρήστες των πλατφορμών του SoftLab. <br/>
Οι πλατφόρμες που διατίθενται μέσω του SoftLab είναι <br/>
ο <a href="http://courses.softlab.ntua.gr/progintro/" target="_blank">Novice</a>,
o <a href="http://grader.softlab.ntua.gr/" target="_blank">Grader</a>, o <a href="http://grader.softlab.ntua.gr/" target="_blank">Grader</a>,
το <a href="https://moodle.softlab.ntua.gr/" target="_blank">Moodle</a> και το <a href="https://moodle.softlab.ntua.gr/" target="_blank">Moodle</a> και
ο <a href="http://plgrader.softlab.ntua.gr/" target="_blank">PLgrader</a>. ο <a href="http://plgrader.softlab.ntua.gr/" target="_blank">PLgrader</a>.
...@@ -71,19 +84,19 @@ ...@@ -71,19 +84,19 @@
</div> </div>
{% block content %} {% block content %}
<p style="text-align:center;">Αυτή η ιστοσελίδα δεν έχει ακόμα περιεχόμενο... :(</p> <p>Αυτή η ιστοσελίδα δεν έχει ακόμα περιεχόμενο... :(</p>
{% endblock %} {% endblock %}
<div class="footer" id="footer"> <div class="footer" id="footer">
<table align="center" width="1000px"> <table>
<tr> <tr>
<td> <td style="padding-right:30px; width:240px;">
<p>&copy; {% now "Y" %} SoftLab</p> <p>&copy; {% now "Y" %} SoftLab</p>
</td> </td>
<td> <td style="width:240px;">
<p><a href="mailto:webmaster@courses.softlab.ntua.gr">Επικοινωνία με Webmaster</a></p> <p><a href="mailto:webmaster@courses.softlab.ntua.gr">Επικοινωνία με Webmaster</a></p>
</td> </td>
<td> <td style="padding-left:30px; width:240px;">
<p> <p>
Αναπτύxθηκε σε <br/> Αναπτύxθηκε σε <br/>
<a href="https://www.python.org/" target="_blank">Python</a>, <a href="https://www.python.org/" target="_blank">Python</a>,
......
...@@ -7,29 +7,54 @@ ...@@ -7,29 +7,54 @@
{% block content %} {% block content %}
<h4>Είσοδος Χρήστη/Διαχειριστή</h4> <h4>Είσοδος Χρήστη/Διαχειριστή</h4>
<form name="login" style="text-align:center;" action="{% url 'login' %}" method="POST"> <form name="login" style="text-align:center;" action="{% url 'submit_login' %}" method="POST">
{% csrf_token %} {% csrf_token %}
<span>&nbsp; &nbsp; Είσοδος ως:</span> <table>
<select name="platforms"> <tr>
<option value="admin">διαχειριστής του SLUB</option> <td style="text-align:right;">Είσοδος ως:</td>
<option value="novice" selected>χρήστης του Novice</option> <td style="text-align:left;">
<option value="grader">χρήστης του Grader</option> <select name="platform">
<option value="moodle">χρήστης του Moodle</option> <option value="SLUB">διαχειριστής του SLUB</option>
<option value="plgrader"> χρήστης του PLgrader</option> {% for platform in platform_list %}
<option value="{{ platform }}" {% if platform == "Novice" %}selected{% endif %}>
χρήστης του {{ platform }}
</option>
{% endfor %}
</select> </select>
</td>
</tr>
<tr>
<td style="text-align:right;">Όνομα χρήστη:</td>
<td style="text-align:left;">
{# https://stackoverflow.com/questions/5272433/html5-form-required-attribute-set-custom-validation-message #}
{# https://stackoverflow.com/questions/8078388/hiding-title-tags-on-hover #}
<input type="text" name="username" maxlength="50" onmouseover="this.title='';"
oninput="setCustomValidity('');" oninvalid="setCustomValidity('Παρακαλούμε συμπληρώστε το πεδίο!');" required/>
</td>
</tr>
<tr>
<td style="text-align:right;">Κωδικός πρόσβασης:</td>
<td style="text-align:left;">
{# https://stackoverflow.com/questions/5272433/html5-form-required-attribute-set-custom-validation-message #}
{# https://stackoverflow.com/questions/8078388/hiding-title-tags-on-hover #}
<input type="password" name="password" maxlength="50" onmouseover="this.title='';"
oninput="setCustomValidity('');" oninvalid="setCustomValidity('Παρακαλούμε συμπληρώστε το πεδίο!');" required/>
</td>
</tr>
</table>
<br/> <br/>
<span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Όνομα χρήστη:</span> <button type="submit" value="login" name="login"><strong>Είσοδος</strong></button>
<input type="text" name="username" onmouseover="this.title='';" oninput="setCustomValidity('');" oninvalid="setCustomValidity('Παρακαλούμε συμπληρώστε το πεδίο!');" required/>
<br/>
<span>Κωδικός πρόσβασης:</span>
<input type="password" name="password" onmouseover="this.title='';" oninput="setCustomValidity('');" oninvalid="setCustomValidity('Παρακαλούμε συμπληρώστε το πεδίο!');" required/>
<br/>
<br/>
<button type="submit" value="login" name="login">
<strong>Είσοδος</strong>
</button>
</form> </form>
{% if error_messages %}
{% for message in error_messages %}
<p style="color:red; font-size:14px;">
<img src="https://moodle.softlab.ntua.gr/theme/image.php/softlab/core/1522849333/i/warning" alt="Σφάλμα"/>
<strong>{{ message | linebreaksbr }}</strong>
</p>
{% endfor %}
{% endif %}
<br/> <br/>
{% endblock %} {% endblock %}
{% extends "base.html" %}
{% block title %}
SLUB - Έξοδος Χρήστη/Διαχειριστή
{% endblock %}
{% block content %}
<h4>Έξοδος Χρήστη/Διαχειριστή</h4>
<p>Εξήλθατε με επιτυχία! :)</p>
<p><a href="{% url 'default_login' %}">Θα θέλατε μήπως να εισέλθετε ξανά;</a></p>
{% endblock %}
* W3Schools - HTML5 Tutorial: https://www.w3schools.com/Html/default.asp
* Django References - Templates: https://docs.djangoproject.com/en/2.0/ref/templates/
{% extends "base.html" %}
{% block title %}
SLUB - Αρχική Σελίδα Χρήστη
{% endblock %}
{% block content %}
<div class="bar" id="in-session-bar" style="padding-top:10px; padding-bottom:10px;">Έχετε εισέλθει επιτυχώς! :)</div>
<h4>Αρχική Σελίδα Χρήστη</h4>
<p><a href="{% url 'logout' %}">Έξοδος</a></p>
{% endblock %}
...@@ -14,9 +14,12 @@ Including another URLconf ...@@ -14,9 +14,12 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.urls import re_path from django.urls import re_path
from usermerge import views from usermerge import views
urlpatterns = [ urlpatterns = [
re_path(r'^login/', views.login, name='login'), re_path(r'^login/default/', views.show_default_login_page, name = 'default_login'),
re_path(r'^login/submit/', views.log_in, name = 'submit_login'),
re_path(r'^home/user/', views.show_user_home_page, name = 'user_home'),
re_path(r'^home/admin/', views.show_admin_home_page, name = 'admin_home'),
re_path(r'^logout/', views.log_out, name = 'logout'),
] ]
from django.shortcuts import render from django.contrib.auth import authenticate, logout
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect, render
from usermerge.auth import login
from usermerge.forms import LoginForm
from usermerge.models import Platform
# Create your views here. # Create your views here.
# https://docs.djangoproject.com/en/2.0/topics/http/views/
# For more information on authenticate() and logout() functions and login_required() decorator, see https://docs.djangoproject.com/en/2.0/topics/auth/default/ . For more information on login() function, see auth.py .
# For more information on working with forms, see https://docs.djangoproject.com/en/2.0/topics/forms/ .
def get_names_of_SoftLab_provided_platforms_from_DB():
"""
Return the list of all the platform names, except for ECE-NTUA (this 'platform' is not provided by SoftLab),
that exist in usermergeDB (see populateDB.py). This list can be used as part of the context
while rendering our application pages, e.g. in the platform drop-down list of the login form.
"""
platform_names = list(Platform.objects.order_by('id').values_list('name', flat = True))
platform_names.remove('ECE-NTUA')
return platform_names
def show_default_login_page(request):
"""
Show login page without any validation/authentication error messages (that is, before submitting any invalid credentials).
"""
return render(request, 'login.html', {'platform_list': get_names_of_SoftLab_provided_platforms_from_DB()})
def log_in(request):
"""
Collect the credentials POSTed via the login form and validate them. If they are not valid, redirect the user to
the login page and display the appropriate validation error messages. Otherwise, authenticate them. If they do
not correspond to any User or Admin instance of usermergeDB (see models.py), redirect the user to the login page and
display the appropriate authentication error message. Otherwise, log the user in and redirect him/her to the
appropriate home page depending on his/her representative model/class (User/Admin from usermerge.models).
"""
form = LoginForm(request.POST)
if form.is_valid():
platform = form.cleaned_data['platform']
username = form.cleaned_data['username']
password = form.cleaned_data['password']
user = authenticate(request, platform = platform, username = username, password = password)
if user:
login(request, user)
# User instances have an ece_id attribute, whereas Admin instances do not.
# Therefore, if user has an ece_id attribute, he/she is a User. Otherwise, he/she is an Admin.
if hasattr(user, 'ece_id'):
return redirect('user_home')
else:
return redirect('admin_home')
else:
return render(request, 'login.html',
{'platform_list': get_names_of_SoftLab_provided_platforms_from_DB(),
'error_messages': ['Τα δηλωθέντα διαπιστευτήρια δεν ανταποκρίνονται σε κανένα χρήστη της βάσης!\n'
'Παρακαλούμε ελέγξτε την ορθότητα των διαπιστευτηρίων και δοκιμάστε ξανά!']})
else:
return render(request, 'login.html',
{'platform_list': get_names_of_SoftLab_provided_platforms_from_DB(),
# https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions
'error_messages': [list(form.errors.values())[k-1][0] for k in range(len(form.errors.values()))]})
@login_required(redirect_field_name = None)
def show_user_home_page(request):
return render(request, 'user_home.html', {})
@login_required(redirect_field_name = None)
def show_admin_home_page(request):
return render(request, 'admin_home.html', {})
@login_required(redirect_field_name = None)
def log_out(request):
"""
Log the authenticated user out and redirect him/her to the logout page.
"""
logout(request)
return render(request, 'logout.html', {})
def login(request):
return render(request, 'login.html', {})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment