Merge branch 'william' into 'master'
William See merge request downhill/vuejs-userservice!2
42
.gitignore
vendored
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
*.log
|
||||||
|
db.sqlite3
|
||||||
|
*.pyc
|
||||||
|
*.DS_Store
|
||||||
|
build/
|
||||||
|
*.egg_info
|
||||||
|
#editors && utilites.
|
||||||
|
*.swp
|
||||||
|
*~
|
||||||
|
__pycache__/
|
||||||
|
.ropeproject/
|
||||||
|
#django
|
||||||
|
local_settings.py
|
||||||
|
|
||||||
|
!.keep
|
||||||
|
media/
|
||||||
|
!media/keep
|
||||||
|
/CACHE/
|
||||||
|
/static/
|
||||||
|
|
||||||
|
\#*#
|
||||||
|
.\#*
|
||||||
|
*~
|
||||||
|
|
||||||
|
secret-key
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
*.db
|
||||||
|
ungleich.db
|
||||||
|
*~*
|
||||||
|
|
||||||
|
secret-key
|
||||||
|
|
||||||
|
*.psd
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
.env
|
||||||
|
*.mo
|
||||||
|
|
||||||
|
venv/
|
||||||
|
dal/ldap_max_uid_file
|
|
@ -1,15 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
# Basic DB to correlate tokens, users and creation time
|
|
||||||
|
|
||||||
class ResetToken(models.Model):
|
|
||||||
|
|
||||||
# users wouldn't use usernames >100 chars
|
|
||||||
user = models.CharField(max_length=100)
|
|
||||||
# Not so sure about tokens, better make it big
|
|
||||||
# should be <100, but big usernames make bigger tokens
|
|
||||||
# if I read that correctly
|
|
||||||
token = models.CharField(max_length=255)
|
|
||||||
# creation time in epoch (UTC)
|
|
||||||
# BigInt just so we are save for the next few decades ;)
|
|
||||||
creation = models.BigIntegerField()
|
|
|
@ -1,254 +0,0 @@
|
||||||
"""
|
|
||||||
Django settings for dal project.
|
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 1.10.7.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/1.10/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/1.10/ref/settings/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import ldap
|
|
||||||
|
|
||||||
from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
|
|
||||||
|
|
||||||
from configparser import ConfigParser
|
|
||||||
|
|
||||||
config = ConfigParser()
|
|
||||||
config.read('userservice.conf')
|
|
||||||
|
|
||||||
# LDAP config
|
|
||||||
|
|
||||||
AUTH_LDAP_SERVER_URI = config['LDAP']['LDAPSERVER']
|
|
||||||
# The search user
|
|
||||||
AUTH_LDAP_BIND_DN = config['LDAP']['SEARCHUSER']
|
|
||||||
# The password for the search user
|
|
||||||
AUTH_LDAP_BIND_PASSWORD = config.get('LDAP','SEARCHUSERPASSWORD', raw=True)
|
|
||||||
# Search union over two ou
|
|
||||||
AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(
|
|
||||||
LDAPSearch("ou=users,dc=ungleich,dc=ch", ldap.SCOPE_SUBTREE, "(uid=%(user)s)"),
|
|
||||||
LDAPSearch("ou=customers,dc=ungleich,dc=ch", ldap.SCOPE_SUBTREE, "(uid=%(user)s)"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Basic User
|
|
||||||
#AUTH_LDAP_USER_DN_TEMPLATE = "uid=%(user)s,ou=users,dc=ungleich,dc=ch"
|
|
||||||
|
|
||||||
# Search over just one ou
|
|
||||||
#AUTH_LDAP_USER_SEARCH = LDAPSearch( LDAPSearch("ou=users,dc=ungleich,dc=ch",
|
|
||||||
# ldap.SCOPE_SUBTREE, "(uid=%(user)s)")
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
|
||||||
# Maps some user keys since ldap has extensive infos
|
|
||||||
#AUTH_LDAP_USER_ATTR_MAP = {"first_name": "givenName", "last_name": "sn"}
|
|
||||||
|
|
||||||
# Maps some profile keys since ldap has extensive infos
|
|
||||||
#AUTH_LDAP_PROFILE_ATTR_MAP = {"home_directory": "homeDirectory"}
|
|
||||||
|
|
||||||
# LDAP config end
|
|
||||||
|
|
||||||
# Django nameko config
|
|
||||||
|
|
||||||
# Where's the Rabbitmq at
|
|
||||||
NAMEKO_CONFIG = {
|
|
||||||
'AMQP_URI': 'amqp://%s' % config['System']['RABBITMQ']
|
|
||||||
}
|
|
||||||
|
|
||||||
# Standard pool size
|
|
||||||
NAMEKO_POOL_SIZE = 4
|
|
||||||
|
|
||||||
# Django nameko config end
|
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
|
|
||||||
STATIC_ROOT = os.path.dirname('/home/downhill/ungleich/vuejsuserservice/dal/dal/static/')
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
|
||||||
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = 'rn=f&ecp#&#escxpk!0e%a$i3sbm$z@5+g4h9q+w7-83*f2f-i'
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = True
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
'bootstrap3',
|
|
||||||
'sekizai',
|
|
||||||
'dal',
|
|
||||||
]
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
|
||||||
'django.middleware.security.SecurityMiddleware',
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Backend for auth
|
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = (
|
|
||||||
'django_auth_ldap.backend.LDAPBackend',
|
|
||||||
# we only use LDAP for this service, so no auth against the standard DB
|
|
||||||
# 'django.contrib.auth.backends.ModelBackend',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'dal.urls'
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
'DIRS': [],
|
|
||||||
'APP_DIRS': True,
|
|
||||||
'OPTIONS': {
|
|
||||||
'context_processors': [
|
|
||||||
'django.template.context_processors.debug',
|
|
||||||
'django.template.context_processors.request',
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
'sekizai.context_processors.sekizai',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
WSGI_APPLICATION = 'dal.wsgi.application'
|
|
||||||
|
|
||||||
# Django Bootstrap - Settings
|
|
||||||
# Added Configuration for bootstrap static files to load over https.
|
|
||||||
BOOTSTRAP3 = {
|
|
||||||
|
|
||||||
# The URL to the jQuery JavaScript file
|
|
||||||
'jquery_url': '//code.jquery.com/jquery.min.js',
|
|
||||||
|
|
||||||
# The Bootstrap base URL
|
|
||||||
'base_url': '//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/',
|
|
||||||
|
|
||||||
# The complete URL to the Bootstrap CSS file
|
|
||||||
# (None means derive it from base_url)
|
|
||||||
'css_url': None,
|
|
||||||
|
|
||||||
# The complete URL to the Bootstrap CSS file (None means no theme)
|
|
||||||
'theme_url': None,
|
|
||||||
|
|
||||||
# The complete URL to the Bootstrap JavaScript file
|
|
||||||
# (None means derive it from base_url)
|
|
||||||
'javascript_url': None,
|
|
||||||
|
|
||||||
# Put JavaScript in the HEAD section of the HTML document
|
|
||||||
# (only relevant if you use bootstrap3.html)
|
|
||||||
'javascript_in_head': False,
|
|
||||||
|
|
||||||
# Include jQuery with Bootstrap JavaScript
|
|
||||||
# (affects django-bootstrap3 template tags)
|
|
||||||
'include_jquery': False,
|
|
||||||
|
|
||||||
# Label class to use in horizontal forms
|
|
||||||
'horizontal_label_class': 'col-md-3',
|
|
||||||
|
|
||||||
# Field class to use in horizontal forms
|
|
||||||
'horizontal_field_class': 'col-md-9',
|
|
||||||
|
|
||||||
# Set HTML required attribute on required fields
|
|
||||||
'set_required': True,
|
|
||||||
|
|
||||||
# Set HTML disabled attribute on disabled fields
|
|
||||||
'set_disabled': False,
|
|
||||||
|
|
||||||
# Set placeholder attributes to label if no placeholder is provided
|
|
||||||
'set_placeholder': True,
|
|
||||||
|
|
||||||
# Class to indicate required (better to set this in your Django form)
|
|
||||||
'required_css_class': '',
|
|
||||||
|
|
||||||
# Class to indicate error (better to set this in your Django form)
|
|
||||||
'error_css_class': 'has-error',
|
|
||||||
|
|
||||||
# Class to indicate success, meaning the field has valid input
|
|
||||||
# (better to set this in your Django form)
|
|
||||||
'success_css_class': 'has-success',
|
|
||||||
|
|
||||||
# Renderers (only set these if you have studied the source and understand
|
|
||||||
# the inner workings)
|
|
||||||
'formset_renderers': {
|
|
||||||
'default': 'bootstrap3.renderers.FormsetRenderer',
|
|
||||||
},
|
|
||||||
'form_renderers': {
|
|
||||||
'default': 'bootstrap3.renderers.FormRenderer',
|
|
||||||
},
|
|
||||||
'field_renderers': {
|
|
||||||
'default': 'bootstrap3.renderers.FieldRenderer',
|
|
||||||
'inline': 'bootstrap3.renderers.InlineFieldRenderer',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
|
||||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
|
||||||
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
|
||||||
# https://docs.djangoproject.com/en/1.10/topics/i18n/
|
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
|
||||||
|
|
||||||
TIME_ZONE = 'UTC'
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
USE_L10N = True
|
|
||||||
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/1.10/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
|
|
@ -1,14 +0,0 @@
|
||||||
<title> Userdata changed. </title>
|
|
||||||
|
|
||||||
<h2> The data for {{user}} has been changed. </h2>
|
|
||||||
<br><br>
|
|
||||||
<ul>
|
|
||||||
<li> Username: {{user}} </li>
|
|
||||||
<li> Firstname: {{firstname}} </li>
|
|
||||||
<li> Lastname: {{lastname}} </li>
|
|
||||||
<li> Email: {{email}} </li>
|
|
||||||
</ul>
|
|
||||||
<br><br>
|
|
||||||
<form action={% url 'index' %} method="get">
|
|
||||||
<input type="submit" value="Back to indexpage">
|
|
||||||
</form>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<title> Password for {{user}} changed. </title>
|
|
||||||
|
|
||||||
<h2> The password for {{user}} has been changed. </h2>
|
|
||||||
<br><br>
|
|
||||||
<form action={% url 'index' %} method="get">
|
|
||||||
<input type="submit" value="Back to indexpage">
|
|
||||||
</form>
|
|
|
@ -1,20 +0,0 @@
|
||||||
<title> Changing the password for {{user}} </title>
|
|
||||||
|
|
||||||
<h2> Changing the password for {{user}} </h2>
|
|
||||||
<br><br>
|
|
||||||
<form action={% url 'index' %} method="get">
|
|
||||||
<input type="submit" value="Back to indexpage">
|
|
||||||
</form>
|
|
||||||
<br><br>
|
|
||||||
To change the password for {{user}}, please supply
|
|
||||||
<form action={% url 'change_password' %} method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<br>The old password:<br>
|
|
||||||
<input type="password" name="oldpassword" id="oldpassword">
|
|
||||||
<br><br>The new password (at least 8 characters):<br>
|
|
||||||
<input type="password" name="password1" id="password1">
|
|
||||||
<br>Please repeat the new Password:<br>
|
|
||||||
<input type="password" name="password2" id="password2">
|
|
||||||
<br><br>
|
|
||||||
<input type="submit" value="Submit">
|
|
||||||
</form>
|
|
|
@ -1,19 +0,0 @@
|
||||||
<title> Changing user data for {{user}} </title>
|
|
||||||
|
|
||||||
<h2> Changing user data for {{user}} </h2>
|
|
||||||
<br><br>
|
|
||||||
<form action={% url 'index' %} method="get">
|
|
||||||
<input type="submit" value="Back to indexpage">
|
|
||||||
</form>
|
|
||||||
<br><br>
|
|
||||||
<form action={% url 'change_data' %} method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<br>Firstname:<br>
|
|
||||||
<input type="text" name="firstname" id="firstname" value="{{firstname}}">
|
|
||||||
<br><br>Lastname:<br>
|
|
||||||
<input type="text" name="lastname" id="lastname" value="{{lastname}}">
|
|
||||||
<br><br>Email:<br>
|
|
||||||
<input type="text" name="email" id="email" value="{{email}}">
|
|
||||||
<br><br>
|
|
||||||
<input type="submit" value="Submit">
|
|
||||||
</form>
|
|
|
@ -1,18 +0,0 @@
|
||||||
<title> Deleting an Account </title>
|
|
||||||
|
|
||||||
<h2> Deleting an Account </h2>
|
|
||||||
<br><br>
|
|
||||||
<form action={% url 'index' %} method="get">
|
|
||||||
<input type="submit" value="Back to indexpage">
|
|
||||||
</form>
|
|
||||||
<br><br>
|
|
||||||
To delete an account, please type the username and password below:
|
|
||||||
<form action={% url 'account_delete' %} method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<br><br>Username:<br>
|
|
||||||
<input type="text" name="username" id="username">
|
|
||||||
<br><br>Password:<br>
|
|
||||||
<input type="password" name="password" id="password">
|
|
||||||
<br><br>
|
|
||||||
<input type="submit" value="Submit">
|
|
||||||
</form>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<title> Deleted user {{user}} </title>
|
|
||||||
|
|
||||||
<h2> The user {{user}} was deleted from our system. </h2>
|
|
||||||
<br>
|
|
||||||
<form action={% url 'index' %} method="get">
|
|
||||||
<input type="submit" value="Back to indexpage">
|
|
||||||
</form>
|
|
|
@ -1,17 +0,0 @@
|
||||||
<title> An error has occurred! </title>
|
|
||||||
|
|
||||||
<h2> We are sorry, an error has occured while handling your request. </h2>
|
|
||||||
|
|
||||||
While trying to {{service}}, an error was encountered: {{error}}
|
|
||||||
<br><br>
|
|
||||||
You can try to:
|
|
||||||
<br>
|
|
||||||
{% if urlname %}
|
|
||||||
<form action={% url urlname %} method="get">
|
|
||||||
<input type="submit" value="Go back and try again">
|
|
||||||
</form>
|
|
||||||
<br>or<br>
|
|
||||||
{% endif %}
|
|
||||||
<form action={% url 'index' %} method="get">
|
|
||||||
<input type="submit" value="Go to the indexpage">
|
|
||||||
</form>
|
|
|
@ -1,44 +0,0 @@
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.col-lg-12 {
|
|
||||||
background-color: grey;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.list-inline {
|
|
||||||
background-color: grey;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-12">
|
|
||||||
<ul class="list-inline">
|
|
||||||
<li class="col-lg-12">
|
|
||||||
<a href="#">Home</a>
|
|
||||||
</li>
|
|
||||||
<li class="footer-menu-divider">⋅</li>
|
|
||||||
<li>
|
|
||||||
<a href="#about">How it works</a></li>
|
|
||||||
<li class="footer-menu-divider">⋅</li>
|
|
||||||
<li>
|
|
||||||
<a href="#about">Your infrastructure</a></li>
|
|
||||||
<li>⋅</li>
|
|
||||||
<li>
|
|
||||||
<a href="#about">Our infrastructure</a></li>
|
|
||||||
<li class="footer-menu-divider">⋅</li>
|
|
||||||
<li>
|
|
||||||
<a href="#services">Pricing</a>
|
|
||||||
</li>
|
|
||||||
<li class="footer-menu-divider">⋅</li>
|
|
||||||
<li>
|
|
||||||
<a href="#contact">Contact</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p class="copyright text-muted small">Copyright © ungleich glarus ag {% now "Y" %}. {% trans "All Rights Reserved" %}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
|
@ -1,45 +0,0 @@
|
||||||
{% extends "base_short.html" %}
|
|
||||||
{% load staticfiles bootstrap3 %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.auth-footer {
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
a:link { color: #000000 }
|
|
||||||
</style>
|
|
||||||
<div class="auth_container">
|
|
||||||
<div class="auth_bg"></div>
|
|
||||||
<div class="auth_center">
|
|
||||||
<div class="auth_content">
|
|
||||||
<div class="auth-box">
|
|
||||||
<h2 class="section-heading allcaps"> Login </h2>
|
|
||||||
{% include 'includes/_messages.html' %}
|
|
||||||
<form action={% url 'index' %} method="post" class="form" nonvalidated>
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="form-group"><label class="sr-only control-label" for="username">Username</label><input class="form-control" type="text" name="username" id="username" placeholder="Username"></div>
|
|
||||||
<div class="form-group"><label class="sr-only control-label" for="password">Password</label><input class="form-control" type="password" name="password" id="password" placeholder="Password"></div>
|
|
||||||
<br><br>
|
|
||||||
<button type="submit" class="btn choice-btn"> Log in </button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div class="auth-footer">
|
|
||||||
<div>
|
|
||||||
Don't have an account yet?
|
|
||||||
<a href={% url 'register' %}> Sign up </a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
or <a href={% url 'reset_password' %}> Reset your password </a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
<title> Login failed! </title>
|
|
||||||
|
|
||||||
<h2> Sorry, but your login has failed </h2>
|
|
||||||
<br><br>This service runs for our LDAP users, so maybe you don't already have an LDAP account with us? If so, please register one.
|
|
||||||
<form action={% url 'register' %} method="get">
|
|
||||||
<input type="submit" value="Register an user">
|
|
||||||
</form>
|
|
||||||
<br><br>
|
|
||||||
<form action={% url 'index' %} method="get">
|
|
||||||
<input type="submit" value="Back to indexpage">
|
|
||||||
</form>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<title> You must be logged in to access this page </title>
|
|
||||||
|
|
||||||
<h2> You must be logged in to access this page </h2>
|
|
||||||
<br><br>
|
|
||||||
<form action={% url 'index' %} method="get">
|
|
||||||
<input type="submit" value="Back to indexpage">
|
|
||||||
</form>
|
|
|
@ -1,27 +0,0 @@
|
||||||
<title> Register an user at ungleich </title>
|
|
||||||
|
|
||||||
<h2> Register an user at ungleich </h2>
|
|
||||||
<br><br>
|
|
||||||
<form action={% url 'index' %} method="get">
|
|
||||||
<input type="submit" value="Back to indexpage">
|
|
||||||
</form>
|
|
||||||
<br><br>
|
|
||||||
To register yourself an user, please fill out the fields below:
|
|
||||||
<br>
|
|
||||||
<form action={% url 'register' %} method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<br>Username (alphanumeric):<br>
|
|
||||||
<input type="text" name="username" id="username">
|
|
||||||
<br>Password (at least 8 characters):<br>
|
|
||||||
<input type="password" name="password1" id="password1">
|
|
||||||
<br>Please confirm your Password:<br>
|
|
||||||
<input type="password" name="password2" id="password2">
|
|
||||||
<br>Firstname:<br>
|
|
||||||
<input type="text" name="firstname" id="firstname">
|
|
||||||
<br>Lastname:<br>
|
|
||||||
<input type="text" name="lastname" id="lastname">
|
|
||||||
<br>Emailaddress:<br>
|
|
||||||
<input type="text" name="email" id="email">
|
|
||||||
<br>
|
|
||||||
<input type="submit" value="Submit">
|
|
||||||
</form>
|
|
|
@ -1,13 +0,0 @@
|
||||||
<title> Password reset </title>
|
|
||||||
|
|
||||||
<h2> Password reset </h2>
|
|
||||||
<br><br>
|
|
||||||
To reset your password, please enter your username below. You will get an email with a link to change your password.
|
|
||||||
<br><br>
|
|
||||||
<form action={% url 'reset_password' %} method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
Username:<br>
|
|
||||||
<input type="text" name="user" id="user">
|
|
||||||
<br>
|
|
||||||
<input type="submit" value="Submit">
|
|
||||||
</form>
|
|
|
@ -1,14 +0,0 @@
|
||||||
<title> Set new password for {{user}} </title>
|
|
||||||
|
|
||||||
<h2> Please set new password for {{user}} </h2>
|
|
||||||
<br><br>
|
|
||||||
<form action={% url 'reset' %} method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
New Password:<br>
|
|
||||||
<input type="password" name="password1" id="password1">
|
|
||||||
<br>Please confirm new password:<br>
|
|
||||||
<input type="password" name="password2" id="password2">
|
|
||||||
<br>
|
|
||||||
<input type="hidden" name="user" id="user" value="{{user}}">
|
|
||||||
<input type="submit" value="Submit">
|
|
||||||
</form>
|
|
|
@ -1,10 +0,0 @@
|
||||||
<title> Reset request processed and confirmation email sent </title>
|
|
||||||
|
|
||||||
<h2> Reset request processed and confirmation email sent </h2>
|
|
||||||
<br><br>
|
|
||||||
You will shortly get the confirmation email to confirm that you wish to reset the password for {{user}}.<br>
|
|
||||||
Please follow the link in the email to reset your password.
|
|
||||||
<br><br>
|
|
||||||
<form action={% url 'index' %} method="get">
|
|
||||||
<input type="submit" value="Back to indexpage">
|
|
||||||
</form>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<title> User {{ user }} created. </title>
|
|
||||||
|
|
||||||
<h2> User {{ user }} was successfully created. </h2>
|
|
||||||
<br><br>
|
|
||||||
<form action={% url 'index' %} method="get">
|
|
||||||
<input type="submit" value="Back to Indexpage">
|
|
||||||
</form>
|
|
|
@ -1,24 +0,0 @@
|
||||||
<title> Options for {{user}} </title>
|
|
||||||
|
|
||||||
<h2> Welcome, {{user}} </h2>
|
|
||||||
<br><br>
|
|
||||||
You have the following options:
|
|
||||||
<br>
|
|
||||||
<form action={% url 'change_data' %} method="get">
|
|
||||||
<input type="submit" value="Change your userdata">
|
|
||||||
</form>
|
|
||||||
<br>
|
|
||||||
<form action={% url 'change_password' %} method="get">
|
|
||||||
<input type="submit" value="Change your password">
|
|
||||||
</form>
|
|
||||||
<br>
|
|
||||||
<form action={% url 'reset_password' %} method="get">
|
|
||||||
<input type="submit" value="Reset your password">
|
|
||||||
</form>
|
|
||||||
<br>
|
|
||||||
<form action={% url 'account_delete' %} method="get">
|
|
||||||
<input type="submit" value="Delete your account">
|
|
||||||
</form>
|
|
||||||
<form action={% url 'logout' %} method="get">
|
|
||||||
<input type="submit" value="Logout">
|
|
||||||
</form>
|
|
437
dal/dal/views.py
|
@ -1,437 +0,0 @@
|
||||||
# Imports from django
|
|
||||||
from django.shortcuts import render
|
|
||||||
from django.views.generic import View
|
|
||||||
from django.contrib.auth import authenticate, login, logout
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.http import HttpResponse, HttpResponseRedirect
|
|
||||||
from django.core.validators import validate_email, ValidationError
|
|
||||||
from django.urls import reverse_lazy
|
|
||||||
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
|
||||||
from django.core.mail import EmailMessage
|
|
||||||
from .models import ResetToken
|
|
||||||
|
|
||||||
# Imports for the extra stuff not in django
|
|
||||||
# django_nameko is an extra module, so gets put in here
|
|
||||||
from base64 import b64encode, b64decode
|
|
||||||
from datetime import datetime
|
|
||||||
from django_nameko import get_pool
|
|
||||||
from random import choice, randint
|
|
||||||
import string
|
|
||||||
from configparser import ConfigParser
|
|
||||||
|
|
||||||
config = ConfigParser()
|
|
||||||
config.read('userservice.conf')
|
|
||||||
|
|
||||||
# Check to see if the username is already taken
|
|
||||||
# Helper function, not to be set up as a view
|
|
||||||
# Check the LDAP if the user exists
|
|
||||||
def check_user_exists(username):
|
|
||||||
with get_pool().next() as rpc:
|
|
||||||
return rpc.userlookup.lookup(username)
|
|
||||||
|
|
||||||
# To trick the tokengenerator to work with us, because we don't really
|
|
||||||
# have the expected user Class since we are reading the user from a form
|
|
||||||
# We store the tokens and don't have to use the check function,
|
|
||||||
# some one time data works fine.
|
|
||||||
|
|
||||||
class LastLogin():
|
|
||||||
|
|
||||||
def replace(self, microsecond=0, tzinfo=None):
|
|
||||||
return randint(1,100000)
|
|
||||||
|
|
||||||
class PseudoUser():
|
|
||||||
# easiest way to handle the check for lastlogin
|
|
||||||
last_login = LastLogin()
|
|
||||||
# random alphanumeric strings for primary key and password, just used for token generation
|
|
||||||
pk = ''.join(choice(string.ascii_letters + string.digits) for _ in range(20))
|
|
||||||
password = ''.join(choice(string.ascii_letters + string.digits) for _ in range(30))
|
|
||||||
|
|
||||||
|
|
||||||
# The index page
|
|
||||||
# If there's a session open, it will give the user the options he/she/it can do, if not,
|
|
||||||
# it will show a landing page explaining what this is and prompt them to login
|
|
||||||
|
|
||||||
class Index(View):
|
|
||||||
|
|
||||||
# Basic binary choice, if it is an authenticated user, go straight to the options page,
|
|
||||||
# if not, then show the landing page
|
|
||||||
def get(self, request):
|
|
||||||
if request.user.is_authenticated:
|
|
||||||
return render(request, 'useroptions.html', { 'user': request.user } )
|
|
||||||
return render(request, 'landing.html')
|
|
||||||
|
|
||||||
# Basically does the same as the GET request, just with trying to login the user beforehand
|
|
||||||
# Shows an errorpage if authentication fails, since just looping to the landing page
|
|
||||||
# would be frustrating
|
|
||||||
def post(self, request):
|
|
||||||
username = request.POST.get('username')
|
|
||||||
password = request.POST.get('password')
|
|
||||||
pwd = r'%s' % password
|
|
||||||
user = authenticate(request, username=username, password=pwd)
|
|
||||||
if user is not None:
|
|
||||||
login(request, user)
|
|
||||||
return render(request, 'useroptions.html', { 'user': user } )
|
|
||||||
return render(request, 'loginfailed.html')
|
|
||||||
|
|
||||||
|
|
||||||
# Registering a user
|
|
||||||
|
|
||||||
class Register(View):
|
|
||||||
|
|
||||||
# Someone wants to register, throw up the page for that
|
|
||||||
def get(self, request):
|
|
||||||
return render(request, 'registeruser.html')
|
|
||||||
|
|
||||||
# Someone filled out the register page, do some basic checks and throw it at nameko
|
|
||||||
def post(self, request):
|
|
||||||
# message for the error template
|
|
||||||
service = 'register an user'
|
|
||||||
# urlname for 'go back' on the errorpage
|
|
||||||
urlname = 'register'
|
|
||||||
username = request.POST.get('username')
|
|
||||||
if username == "" or not username:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Please supply a username.' } )
|
|
||||||
# Check to see if username is already taken
|
|
||||||
# isalnum() may be a bit harsh, but is the most logical choice to make sure it's a username we
|
|
||||||
# can use
|
|
||||||
if not username.isalnum():
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Username has to be alphanumeric.' } )
|
|
||||||
elif check_user_exists(username):
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'User already exists.' } )
|
|
||||||
password1 = request.POST.get('password1')
|
|
||||||
password2 = request.POST.get('password2')
|
|
||||||
# check if the supplied passwords match
|
|
||||||
if password1 != password2:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service,
|
|
||||||
'error': 'Your passwords did not match. Please supply the same password twice.' } )
|
|
||||||
# check for at least a bit of length on the password
|
|
||||||
if len(password1) < 8:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service,
|
|
||||||
'error': 'Your password is too short, please use a longer one. At least 8 characters.' } )
|
|
||||||
|
|
||||||
email = request.POST.get('email')
|
|
||||||
# Is the emailaddress valid?
|
|
||||||
try:
|
|
||||||
validate_email(email)
|
|
||||||
except ValidationError:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'The supplied email address is invalid.' } )
|
|
||||||
|
|
||||||
firstname = request.POST.get('firstname')
|
|
||||||
lastname = request.POST.get('lastname')
|
|
||||||
if firstname == "" or not firstname or lastname == "" or not lastname:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Please enter your firstname and lastname.' } )
|
|
||||||
# throw it to nameko to create the user
|
|
||||||
with get_pool().next() as rpc:
|
|
||||||
# so nothing strange happens if there are escapable chars
|
|
||||||
pwd = r'%s' % password1
|
|
||||||
result = rpc.createuser.create_user(username, pwd, firstname, lastname, email)
|
|
||||||
if result == True:
|
|
||||||
return render(request, 'usercreated.html', { 'user': username } )
|
|
||||||
else:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': result } )
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Change user data for logged in users
|
|
||||||
|
|
||||||
class ChangeData(View):
|
|
||||||
|
|
||||||
|
|
||||||
# provide the form for the change request
|
|
||||||
def get(self, request):
|
|
||||||
urlname = 'change_data'
|
|
||||||
service = 'get default data for logged in user'
|
|
||||||
if not request.user.is_authenticated:
|
|
||||||
return render(request, 'mustbeloggedin.html')
|
|
||||||
user = request.user
|
|
||||||
login(request, user)
|
|
||||||
# get basic data (firstname, lastname, email)
|
|
||||||
with get_pool().next() as rpc:
|
|
||||||
(state, firstname, lastname, email) = rpc.getuserdata.get_data(str(request.user))
|
|
||||||
# If it throws an error, the errormessage gets put into firstname.. not great naming, but works best this way
|
|
||||||
if state == "error":
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': firstname } )
|
|
||||||
# The template puts the old data as standard in the fields
|
|
||||||
else:
|
|
||||||
return render(request, 'changeuserdata.html', { 'user': str(request.user), 'firstname': firstname, 'lastname': lastname, 'email': email } )
|
|
||||||
|
|
||||||
# get the change request
|
|
||||||
def post(self, request):
|
|
||||||
# variables for the error page
|
|
||||||
service = 'change user data'
|
|
||||||
urlname = 'change_data'
|
|
||||||
|
|
||||||
# Only logged in users may change data
|
|
||||||
if not request.user.is_authenticated:
|
|
||||||
return render(request, 'mustbeloggedin.html')
|
|
||||||
|
|
||||||
user = str(request.user)
|
|
||||||
firstname = request.POST.get('firstname')
|
|
||||||
lastname = request.POST.get('lastname')
|
|
||||||
email = request.POST.get('email')
|
|
||||||
|
|
||||||
# Some sanity checks for the supplied data
|
|
||||||
if firstname == "":
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Please enter a firstname.' } )
|
|
||||||
elif lastname == "":
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Please enter a lastname.' } )
|
|
||||||
elif email == "":
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Please enter an email.' } )
|
|
||||||
try:
|
|
||||||
validate_email(email)
|
|
||||||
except ValidationError:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'The supplied email address is invalid.' } )
|
|
||||||
# Trying to change the data
|
|
||||||
with get_pool().next() as rpc:
|
|
||||||
result = rpc.changeuserdata.change_data(user, firstname, lastname, email)
|
|
||||||
# Data change worked
|
|
||||||
if result == True:
|
|
||||||
return render(request, 'changeddata.html', { 'user': user, 'firstname': firstname, 'lastname': lastname, 'email': email } )
|
|
||||||
# Data change did not work, display error
|
|
||||||
else:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': result } )
|
|
||||||
|
|
||||||
|
|
||||||
# Resets the password for a user
|
|
||||||
# Sends email to the user with a link to reset the password
|
|
||||||
|
|
||||||
class ResetPassword(View):
|
|
||||||
|
|
||||||
# Presents the form with some information
|
|
||||||
def get(self, request):
|
|
||||||
return render(request, 'resetpassword.html')
|
|
||||||
|
|
||||||
# gets the data from confirming the reset request and checks if it was not a misclick
|
|
||||||
# (by having the user type in his username)
|
|
||||||
def post(self, request):
|
|
||||||
urlname = 'reset_password'
|
|
||||||
service = 'send a password reset request'
|
|
||||||
user = request.POST.get('user')
|
|
||||||
# First, check if the user exists
|
|
||||||
if not check_user_exists(user):
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'The user does not exist.' } )
|
|
||||||
# user exists, so try to get email
|
|
||||||
with get_pool().next() as rpc:
|
|
||||||
(state, tmp1, tmp2, email) = rpc.getuserdata.get_data(user)
|
|
||||||
# Either error with the datalookup or no email provided
|
|
||||||
if state == "error" or email == 'No email given' or not email:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Unable to retrieve email address for user.' } )
|
|
||||||
# Try to send the email out
|
|
||||||
emailsend = self.email(user, email)
|
|
||||||
# Email got sent out
|
|
||||||
if emailsend == True:
|
|
||||||
return render(request, 'send_resetrequest.html', { 'user': user } )
|
|
||||||
# Error while trying to send email
|
|
||||||
else:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': emailsend } )
|
|
||||||
|
|
||||||
# Sends an email to the user with the 24h active link for a password reset
|
|
||||||
def email(self, user, email):
|
|
||||||
# getting epoch for the time now in UTC to spare us headache with timezones
|
|
||||||
creationtime = int(datetime.utcnow().timestamp())
|
|
||||||
# Construct the data for the email
|
|
||||||
email_from = 'Userservice at ungleich <%s>' % config['EMAIL']['EMAILFROM']
|
|
||||||
to = ['%s <%s>' % (user, email)]
|
|
||||||
subject = 'Password reset request for %s' % user
|
|
||||||
link = self.build_reset_link(user, creationtime)
|
|
||||||
body = 'This is an automated email which was triggered by a reset request for the user %s. Please do not reply to this email.\n' % user
|
|
||||||
body += 'If you received this email in error, please disregard it. If you get multiple emails like this, please contact us to look into potential abuse.\n'
|
|
||||||
body += 'To reset your password, please follow the link below:\n'
|
|
||||||
body += '%s\n\n' % link
|
|
||||||
body += 'The link will remain active for 24 hours.\n'
|
|
||||||
# Build the email
|
|
||||||
mail = EmailMessage(
|
|
||||||
subject=subject,
|
|
||||||
body=body,
|
|
||||||
from_email=email_from,
|
|
||||||
to=to
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
mail.send()
|
|
||||||
result = True
|
|
||||||
except:
|
|
||||||
result = "An error occurred while trying to send the mail."
|
|
||||||
return result
|
|
||||||
|
|
||||||
# Builds the reset link for the email and puts the token into the database
|
|
||||||
def build_reset_link(self, user, epochutc):
|
|
||||||
# set up the data
|
|
||||||
host = 'account-staging.ungleich.ch'
|
|
||||||
tokengen = PasswordResetTokenGenerator()
|
|
||||||
# create some noise for use in the tokengenerator
|
|
||||||
pseudouser = PseudoUser()
|
|
||||||
token = tokengen.make_token(pseudouser)
|
|
||||||
buser = bytes(user, 'utf-8')
|
|
||||||
userpart = b64encode(buser)
|
|
||||||
# create entry into the database
|
|
||||||
newdbentry = ResetToken(user=user, token=token, creation=epochutc)
|
|
||||||
newdbentry.save()
|
|
||||||
# set up the link
|
|
||||||
link = 'https://%s/reset/%s/%s/' % (host, userpart.decode('utf-8'), token)
|
|
||||||
return link
|
|
||||||
|
|
||||||
|
|
||||||
# Catch the resetrequest URL and check it
|
|
||||||
class ResetRequest(View):
|
|
||||||
|
|
||||||
# Gets the URL with user in b64 and the token, and checks it
|
|
||||||
# Also cleans the database
|
|
||||||
def get(self, request, user=None, token=None):
|
|
||||||
# Cleans up outdated tokens
|
|
||||||
# If we expect quite a bit of old tokens, maybe somewhere else is better,
|
|
||||||
# but for now we don't really expect many unused tokens
|
|
||||||
self.clean_db()
|
|
||||||
# If user and token are not supplied by django, it was called from somewhere else, so it's
|
|
||||||
# invalid
|
|
||||||
if user == None or token == None:
|
|
||||||
return HttpResponse('Invalid URL.', status=404)
|
|
||||||
# extract user from b64 format
|
|
||||||
tmp_user = bytes(user, 'utf-8')
|
|
||||||
user = b64decode(tmp_user)
|
|
||||||
user_clean = user.decode('utf-8')
|
|
||||||
# set checks_out = True if token is found in database
|
|
||||||
dbentries = ResetToken.objects.all().filter(user=user_clean)
|
|
||||||
for entry in dbentries:
|
|
||||||
if entry.token == token:
|
|
||||||
# found the token, now delete it since it's used
|
|
||||||
checks_out = True
|
|
||||||
entry.delete()
|
|
||||||
# No token was found
|
|
||||||
if not checks_out:
|
|
||||||
return HttpResponse('Invalid URL.', status=404)
|
|
||||||
# Token was found, supply the form
|
|
||||||
else:
|
|
||||||
return render(request, 'resetpasswordnew.html', { 'user': user_clean } )
|
|
||||||
|
|
||||||
|
|
||||||
# Gets the post form with the new password and sets it
|
|
||||||
def post(self, request):
|
|
||||||
service = 'reset the password'
|
|
||||||
# get the supplied passwords
|
|
||||||
password1 = request.POST.get("password1")
|
|
||||||
password2 = request.POST.get("password2")
|
|
||||||
# get the hidden value of user
|
|
||||||
user = request.POST.get("user")
|
|
||||||
# some checks over the supplied data
|
|
||||||
if user == "" or not user:
|
|
||||||
return render(request, 'error.html', { 'service': service, 'error': 'Something went wrong. Did you use the supplied form?' } )
|
|
||||||
if password1 == "" or not password1 or password2 == "" or not password2:
|
|
||||||
return render(request, 'error.html', { 'service': service, 'error': 'Please supply a password and confirm it.' } )
|
|
||||||
if password1 != password2:
|
|
||||||
return render(request, 'error.html', { 'service': service, 'error': 'The supplied passwords do not match.' } )
|
|
||||||
if len(password1) < 8:
|
|
||||||
return render(request, 'error.html', { 'service': service, 'error': 'The password is too short, please use a longer one. At least 8 characters.' } )
|
|
||||||
# everything checks out, now change the password
|
|
||||||
with get_pool().next() as rpc:
|
|
||||||
pwd = r'%s' % password1
|
|
||||||
result = rpc.changepassword.change_password(user, pwd)
|
|
||||||
# password change successfull
|
|
||||||
if result == True:
|
|
||||||
return render(request, 'changedpassword.html', { 'user': user } )
|
|
||||||
# Something went wrong while changing the password
|
|
||||||
else:
|
|
||||||
return render(request, 'error.html', { 'service': service, 'error': result } )
|
|
||||||
|
|
||||||
# Cleans up outdated tokens
|
|
||||||
def clean_db(self):
|
|
||||||
# cutoff time is set to 24h hours
|
|
||||||
# using utcnow() to have no headache with timezones
|
|
||||||
cutoff = int(datetime.utcnow().timestamp()) - (24*60*60)
|
|
||||||
# Get all tokens older than 24 hours
|
|
||||||
oldtokens = ResetToken.objects.all().filter(creation__lt=cutoff)
|
|
||||||
for token in oldtokens:
|
|
||||||
# delete all tokens older than 24 hours
|
|
||||||
token.delete()
|
|
||||||
return True
|
|
||||||
|
|
||||||
# The logged in user can change the password here
|
|
||||||
|
|
||||||
class ChangePassword(View):
|
|
||||||
|
|
||||||
# Presents the page for a logged in user
|
|
||||||
def get(self, request):
|
|
||||||
if not request.user.is_authenticated:
|
|
||||||
return render(request, 'mustbeloggedin.html')
|
|
||||||
return render(request, 'changepassword.html', { 'user': request.user } )
|
|
||||||
|
|
||||||
# Does some checks on the supplied data and changes the password
|
|
||||||
def post(self, request):
|
|
||||||
# Variables for the error page
|
|
||||||
urlname = 'change_password'
|
|
||||||
service = 'change the password'
|
|
||||||
|
|
||||||
if not request.user.is_authenticated:
|
|
||||||
return render(request, 'mustbeloggedin.html')
|
|
||||||
login(request, request.user)
|
|
||||||
|
|
||||||
user = str(request.user)
|
|
||||||
oldpassword = request.POST.get('oldpassword')
|
|
||||||
check = authenticate(request, username=user, password=oldpassword)
|
|
||||||
# Is the right password for the user supplied?
|
|
||||||
if check is None:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Wrong password for the user.' } )
|
|
||||||
|
|
||||||
password1 = request.POST.get('password1')
|
|
||||||
password2 = request.POST.get('password2')
|
|
||||||
# Are both passwords from the form the same?
|
|
||||||
if password1 != password2:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service,
|
|
||||||
'error': 'Please check if you typed the same password both times for the new password' } )
|
|
||||||
# Check for password length
|
|
||||||
if len(password1) < 8:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service,
|
|
||||||
'error': 'The password is too short, please use a longer one. At least 8 characters.' } )
|
|
||||||
with get_pool().next() as rpc:
|
|
||||||
# Trying to change the password
|
|
||||||
pwd = r'%s' % password1
|
|
||||||
result = rpc.changepassword.change_password(user, pwd)
|
|
||||||
# Password was changed
|
|
||||||
if result == True:
|
|
||||||
return render(request, 'changedpassword.html', { 'user': user } )
|
|
||||||
# Password not changed, instead got some kind of error
|
|
||||||
else:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': result } )
|
|
||||||
|
|
||||||
|
|
||||||
# Deletes an account
|
|
||||||
class DeleteAccount(View):
|
|
||||||
|
|
||||||
# Show the basic form for deleting an account
|
|
||||||
def get(self, request):
|
|
||||||
return render(request, 'deleteaccount.html')
|
|
||||||
|
|
||||||
# Reads the filled out form
|
|
||||||
def post(self, request):
|
|
||||||
# Variables for error page
|
|
||||||
urlname = 'account_delete'
|
|
||||||
service = 'delete an account'
|
|
||||||
|
|
||||||
# Does the user exist?
|
|
||||||
username = request.POST.get('username')
|
|
||||||
if not check_user_exists(username):
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Unknown user.' } )
|
|
||||||
|
|
||||||
# Do user and password match?
|
|
||||||
password = request.POST.get('password')
|
|
||||||
pwd = r'%s' % password
|
|
||||||
check = authenticate(request, username=username, password=pwd)
|
|
||||||
if check is None:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Wrong password for user.' } )
|
|
||||||
|
|
||||||
# Try to delete the user
|
|
||||||
with get_pool().next() as rpc:
|
|
||||||
result = rpc.deleteuser.delete_user(username)
|
|
||||||
# User deleted
|
|
||||||
if result == True:
|
|
||||||
logout(request)
|
|
||||||
return render(request, 'deleteduser.html', { 'user': username } )
|
|
||||||
# User not deleted, got some kind of error
|
|
||||||
else:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': result } )
|
|
||||||
|
|
||||||
# Log out the session
|
|
||||||
class LogOut(View):
|
|
||||||
|
|
||||||
def get(self, request):
|
|
||||||
logout(request)
|
|
||||||
return HttpResponse("You have been logged out.", status=200)
|
|
10
dal/env.sample
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Create .env to be loaded automatically
|
||||||
|
|
||||||
|
LDAPSERVER="ldap://ldap1.ungleich.ch ldap://ldap2.ungleich.ch"
|
||||||
|
LDAPSEARCHUSER="user here"
|
||||||
|
LDAPSEARCHUSERPASSWORD="password here"
|
||||||
|
|
||||||
|
|
||||||
|
# Space separated list of search bases for users
|
||||||
|
LDAPSEARCH="ou=users,dc=ungleich,dc=ch ou=customers,dc=ungleich,dc=ch"
|
||||||
|
LDAPCREATE="ou=customers,dc=ungleich,dc=ch"
|
31
dal/forms.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.auth import authenticate
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class LoginForm(forms.Form):
|
||||||
|
username = forms.CharField(widget=forms.TextInput())
|
||||||
|
password = forms.CharField(widget=forms.PasswordInput())
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = ['username', 'password']
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
username = self.cleaned_data.get('username')
|
||||||
|
password = self.cleaned_data.get('password')
|
||||||
|
if self.errors:
|
||||||
|
return self.cleaned_data
|
||||||
|
is_auth = authenticate(username=username, password=password)
|
||||||
|
if not is_auth:
|
||||||
|
raise forms.ValidationError(
|
||||||
|
_("Your username and/or password were incorrect.")
|
||||||
|
)
|
||||||
|
# elif is_auth.validated == 0:
|
||||||
|
# raise forms.ValidationError(
|
||||||
|
# _("Your account is not activated yet.")
|
||||||
|
# )
|
||||||
|
return self.cleaned_data
|
||||||
|
|
||||||
|
def clean_username(self):
|
||||||
|
username = self.cleaned_data.get('username')
|
||||||
|
return username
|
37
dal/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# Generated by Django 2.1.7 on 2019-02-24 17:35
|
||||||
|
|
||||||
|
import dal.models
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ResetToken',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('user', models.CharField(max_length=100)),
|
||||||
|
('token', models.CharField(max_length=255)),
|
||||||
|
('creation', models.BigIntegerField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserAccountValidationDetail',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('validated', models.IntegerField(choices=[(0, 'Not validated'), (1, 'Validated')], default=0)),
|
||||||
|
('validation_slug', models.CharField(db_index=True, default=dal.models.get_validation_slug, max_length=50, unique=True)),
|
||||||
|
('date_validation_started', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
0
dal/migrations/__init__.py
Normal file
32
dal/models.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.hashers import make_password
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
# Basic DB to correlate tokens, users and creation time
|
||||||
|
|
||||||
|
class ResetToken(models.Model):
|
||||||
|
|
||||||
|
# users wouldn't use usernames >100 chars
|
||||||
|
user = models.CharField(max_length=100)
|
||||||
|
# Not so sure about tokens, better make it big
|
||||||
|
# should be <100, but big usernames make bigger tokens
|
||||||
|
# if I read that correctly
|
||||||
|
token = models.CharField(max_length=255)
|
||||||
|
# creation time in epoch (UTC)
|
||||||
|
# BigInt just so we are save for the next few decades ;)
|
||||||
|
creation = models.BigIntegerField()
|
||||||
|
|
||||||
|
|
||||||
|
def get_validation_slug():
|
||||||
|
return make_password(None)
|
||||||
|
|
||||||
|
|
||||||
|
class UserAccountValidationDetail(models.Model):
|
||||||
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||||
|
VALIDATED_CHOICES = ((0, 'Not validated'), (1, 'Validated'))
|
||||||
|
validated = models.IntegerField(choices=VALIDATED_CHOICES, default=0)
|
||||||
|
validation_slug = models.CharField(
|
||||||
|
db_index=True, unique=True, max_length=50,
|
||||||
|
default=get_validation_slug
|
||||||
|
)
|
||||||
|
date_validation_started = models.DateTimeField(auto_now_add=True)
|
218
dal/settings.py
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
"""
|
||||||
|
Django settings for dal project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 1.10.7.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/1.10/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/1.10/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from decouple import config, Csv
|
||||||
|
import ldap
|
||||||
|
from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
|
||||||
|
|
||||||
|
# LDAP setup
|
||||||
|
LDAP_SERVER = config('LDAP_SERVER')
|
||||||
|
AUTH_LDAP_SERVER_URI = config('LDAPSERVER')
|
||||||
|
|
||||||
|
LDAP_ADMIN_DN = config('LDAP_ADMIN_DN')
|
||||||
|
LDAP_ADMIN_PASSWORD = config('LDAP_ADMIN_PASSWORD')
|
||||||
|
AUTH_LDAP_BIND_DN = LDAP_ADMIN_DN
|
||||||
|
AUTH_LDAP_BIND_PASSWORD = LDAP_ADMIN_PASSWORD
|
||||||
|
AUTH_LDAP_SERVER = AUTH_LDAP_SERVER_URI
|
||||||
|
|
||||||
|
LDAP_CUSTOMER_DN = config('LDAP_CUSTOMER_DN')
|
||||||
|
LDAP_USERS_DN = config('LDAP_USERS_DN')
|
||||||
|
LDAP_CUSTOMER_GROUP_ID = config('LDAP_CUSTOMER_GROUP_ID', cast=int)
|
||||||
|
|
||||||
|
LDAP_MAX_UID_FILE_PATH = config(
|
||||||
|
'LDAP_MAX_UID_FILE_PATH',
|
||||||
|
default=os.path.join(os.path.abspath(
|
||||||
|
os.path.dirname(__file__)), 'ldap_max_uid_file'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
LDAP_DEFAULT_START_UID = config('LDAP_DEFAULT_START_UID', cast=int)
|
||||||
|
|
||||||
|
# Search union over OUs
|
||||||
|
search_base = config('LDAPSEARCH').split()
|
||||||
|
search_base_ldap = [ LDAPSearch(x, ldap.SCOPE_SUBTREE, "(uid=%(user)s)") for x in search_base ]
|
||||||
|
AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*search_base_ldap)
|
||||||
|
|
||||||
|
AUTH_LDAP_START_TLS = config('LDAP_USE_TLS', default=False, cast=bool)
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
DEBUG = config('DEBUG', default=False, cast=bool)
|
||||||
|
|
||||||
|
EMAIL_FROM_ADDRESS = config('EMAIL_FROM_ADDRESS')
|
||||||
|
|
||||||
|
EMAIL_HOST = config("EMAIL_HOST", default="localhost")
|
||||||
|
EMAIL_PORT = config("EMAIL_PORT", default=25, cast=int)
|
||||||
|
EMAIL_USE_TLS = config("EMAIL_USE_TLS", default=True, cast=bool)
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv())
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'bootstrap3',
|
||||||
|
'dal',
|
||||||
|
'rest_framework'
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = (
|
||||||
|
'django_auth_ldap.backend.LDAPBackend',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'dal.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'dal.wsgi.application'
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/1.10/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/1.10/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
############################# To be fixed
|
||||||
|
|
||||||
|
STATIC_ROOT= os.path.join(BASE_DIR, 'static/')
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SECRET_KEY = config('SECRET_KEY')
|
||||||
|
|
||||||
|
AUTH_LDAP_USER_ATTR_MAP = {
|
||||||
|
"first_name": "givenName",
|
||||||
|
"last_name": "sn",
|
||||||
|
"email": "mail"
|
||||||
|
}
|
||||||
|
|
||||||
|
ENTIRE_SEARCH_BASE = config("ENTIRE_SEARCH_BASE")
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
'disable_existing_loggers': False,
|
||||||
|
'version': 1,
|
||||||
|
'formatters': {
|
||||||
|
'standard': {
|
||||||
|
'format': '%(asctime)s %(levelname)s %(name)s %(funcName)s %(lineno)d: %(message)s'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'handlers': {
|
||||||
|
'default': {
|
||||||
|
'level': 'DEBUG',
|
||||||
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
|
'filename': 'logs/debug.log',
|
||||||
|
'maxBytes': 1024*1024*5,
|
||||||
|
'backupCount': 10,
|
||||||
|
'formatter': 'standard',
|
||||||
|
},
|
||||||
|
'console': {
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if config('ENABLE_DEBUG_LOG', default=False, cast=bool):
|
||||||
|
loggers_dict = {}
|
||||||
|
modules_to_log_list = config(
|
||||||
|
'MODULES_TO_LOG', default='django', cast=Csv()
|
||||||
|
)
|
||||||
|
for custom_module in modules_to_log_list:
|
||||||
|
logger_item = {
|
||||||
|
custom_module: {
|
||||||
|
'handlers': ['default'],
|
||||||
|
'level': 'INFO',
|
||||||
|
'propagate': True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loggers_dict.update(logger_item)
|
||||||
|
|
||||||
|
LOGGING['loggers'] = loggers_dict
|
||||||
|
|
||||||
|
if 'ldap3' in modules_to_log_list:
|
||||||
|
from ldap3.utils.log import (
|
||||||
|
set_library_log_detail_level, OFF, BASIC, NETWORK, EXTENDED
|
||||||
|
)
|
||||||
|
set_library_log_detail_level(BASIC)
|
||||||
|
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_RENDERER_CLASSES': (
|
||||||
|
'rest_framework.renderers.JSONRenderer',
|
||||||
|
)
|
||||||
|
}
|
4
dal/static/datacenterlight/font-awesome/css/font-awesome.min.css
vendored
Normal file
Before Width: | Height: | Size: 434 KiB After Width: | Height: | Size: 434 KiB |
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 394 KiB After Width: | Height: | Size: 394 KiB |
Before Width: | Height: | Size: 298 KiB After Width: | Height: | Size: 298 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 685 KiB After Width: | Height: | Size: 685 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |