Compare commits

..

28 Commits

Author SHA1 Message Date
Nico Schottelius e88cd4bc59 Register add 2021-12-12 20:39:32 +01:00
Nico Schottelius 12ed80292b in between commit 2021-12-12 20:07:32 +01:00
Nico Schottelius 196ab2787e ++notes 2021-12-05 20:53:41 +01:00
Nico Schottelius bdd97f4eeb Begin setup of new auth system 2021-12-05 18:59:19 +01:00
Nico Schottelius 01e8c827ab Add template based on Django 4.0rc1
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
2021-12-05 18:34:25 +01:00
Nico Schottelius 9247b627fd Begin cleanup 2021-12-05 18:19:35 +01:00
Nico Schottelius 27d15a5ddf Add initial Dockerfile 2021-12-05 18:14:32 +01:00
pcoder116 73bf95fe56 Merge branch 'master' into 'master'
Upgrade django version to 2.2.16 from 2.1.7 as seen on prod

See merge request ungleich-public/ungleich-user!8
2020-09-02 17:13:35 +02:00
PCoder 6a3d48e6e1 Upgrade django version to 2.2.16 from 2.1.7 as seen on prod
This upgrade is to patch security fixes included in 2.2.16
2020-09-02 20:24:01 +05:30
Timothee Floure a7b9d70b9d Merge upstream repository and code running on production... 2020-05-24 08:04:25 +02:00
nico14571 0ded060220 Merge branch 'uid-validation' into 'master'
Minimal README, validate username format on registration, fix field name on login

See merge request ungleich-public/ungleich-user!7
2020-05-01 11:52:12 +02:00
wcolmenares 8a8cc5d20e better response messsage 2019-05-30 05:17:20 -04:00
wcolmenares e1b4aac222 fix typho response -> Response 2019-05-30 05:07:06 -04:00
wcolmenares f6b723b76a fix typho in creation link 2019-05-30 05:04:11 -04:00
wcolmenares ce5f88b134 fix hardwritten test 2019-05-26 16:59:52 -04:00
wcolmenares 36d10deaab Added buttons for back to index and show seeds 2019-05-26 16:50:56 -04:00
wcolmenares d37144c541 seed listing view and template 2019-05-26 16:15:07 -04:00
wcolmenares 321d8548da url for seed list 2019-05-26 16:13:58 -04:00
wcolmenares 9576ae9064 Same messages in creation/deletion preventing scape chars errors in password 2019-05-06 11:42:58 -04:00
wcolmenares 9711dc1ecc fix show message error when not valid credentials 2019-05-05 18:12:43 -04:00
wcolmenares 54aa8f474e added rest framework conf 2019-05-05 17:16:14 -04:00
wcolmenares 6795a4c35c remove double requirement package 2019-05-05 17:13:58 -04:00
wcolmenares 4cfff85d7e update requirements 2019-05-05 16:44:27 -04:00
wcolmenares 200699486a rest interface for retrieve-create users seed 2019-05-05 16:44:02 -04:00
wcolmenares 503e31cc69 fix user ldap creation from bytes to string 2019-05-02 21:24:54 -04:00
wcolmenares 86a082c29f Fix typho email verification 2019-05-02 17:36:31 -04:00
wcolmenares d07ff15f69 Add validation email before creation 2019-05-02 17:29:01 -04:00
wcolmenares c4079a1c1d request the username in login instead of the email 2019-05-02 04:10:06 -04:00
31 changed files with 798 additions and 89 deletions

16
Dockerfile Normal file
View File

@ -0,0 +1,16 @@
FROM python:3.10.0-alpine3.15
WORKDIR /usr/src/app
RUN apk add --update --no-cache\
build-base \
openldap-dev\
python3-dev\
&& rm -rf /var/cache/apk/*
# FIX https://github.com/python-ldap/python-ldap/issues/432
RUN echo 'INPUT ( libldap.so )' > /usr/lib/libldap_r.so
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY ungleichuser/ .

View File

@ -4,17 +4,80 @@ This service runs on [account.ungleich.ch](https://account.ungleich.ch/) and
allows customers manage their ungleich account (register, edit mail & password,
...).
Makes use of Django 2.2 LTS (not compatible with 3.0 at the moment) which is
supported until early 2022.
## Status v2
* See below ungleichuser/
* register view created
* Not solved:
* email verification *before* user is created
* Solved
* Finding highest uid, increasing by 1
## Todos for v2:
* Rewrite/create new app Django 4.0 based (ungleichuser)
* Do not use django-auth-ldap, because it uses python-ldap
* Update the template HTML to bootstrap5
* Use ldap3
* Check/verify the ldap registration
* Ensure multiple ldap servers are supported
* Cleanup this readme
* Use the following "captcha" for all forms:
* Extra form field named "random"
* Present the user the following text:
* Create Dockerfile / ensure it works in kubernetes
## Next steps
* Override/use custom form to allow captcha
## Development Setup
* Clone this repository and enter top-level directory.
* (Optional) Setup a Python virtualenv and install dependencies via pip:
- `virtualenv .venv`
- `source .venv/bin/activate`
- `pip install -r requirements.txt`
- Note: you might have to install some OS dependencies (i.e. libldap2, libsasl).
* Configure the `dal` django app (uses the [decouple](https://pypi.org/project/python-decouple/) library underneath)
- Copy `dal/env.sample` to `dal/.env`
- Populate `dal/.env`
```
python3 -m venv venv
. ./venv/bin/activate
pip install -r requirements.txt
``
## Functionality
x Allow user to register in a specific subtree
x Verify that user does not exist in another subtree
x Assign an id
- Allow password reset [via Mail?]
- DB usage: for password reset?
## Parameters
Via environment variables:
* LDAPSERVERS=".." -- White space separated list of LDAP-Servers
* ADMIN_DN="" -- we use this DN to connect to LDAP
* ADMIN_PASSWORD="" -- we use this password to connect to LDAP
* SECRET_KEY
* DEBUG
* ALLOWED_HOSTS
## Views
### Register view
Form: [get]
* username
* password1
* password2
Post receiver: [post]
* E-Mail verification
E-Mail verify: [get]
* Create user
### Password reset view
* captcha
* username or email

View File

@ -65,6 +65,7 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'bootstrap3',
'dal',
'rest_framework'
]
MIDDLEWARE = [
@ -208,3 +209,10 @@ if config('ENABLE_DEBUG_LOG', default=False, cast=bool):
set_library_log_detail_level, OFF, BASIC, NETWORK, EXTENDED
)
set_library_log_detail_level(BASIC)
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
)
}

View File

@ -11,14 +11,13 @@
<meta name="description" content="">
<meta name="author" content="ungleich glarus ag">
<title> Ungleich userservice </title>
<title> Ungleich User Service </title>
<!-- Bootstrap Core CSS -->
<link href="{% static 'datacenterlight/css/bootstrap-3.3.7.min.css' %}" rel="stylesheet">
<!-- Custom CSS -->
<!--<link href=" static 'hosting/css/pricing.css' %}" rel="stylesheet">-->
<link href="{% static 'hosting/css/landing-page.css' %}" rel="stylesheet">
<!-- Custom Fonts -->
@ -34,19 +33,8 @@
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<!-- with 'hosting/img/'|add:hosting|add:'-intro-bg.png' as image_static
<style media="screen" type="text/css">
.intro-header {
background: url(" static image_static %}") no-repeat center center;
}
</style>
-->
<!-- endwith %}-->
<!-- Google analytics -->
<!-- include "google_analytics.html" %} -->
<!-- End Google Analytics -->
</head>
<body>

View File

@ -35,6 +35,8 @@
<button type="submit" class="btn choice-btn btn-block">
{% trans "Change Password" %}
</button>
<hr>
<a class="btn choice-btn btn-block" href="{% url 'index' %}" role="button">Back to Index</a><br>
</div>
</form>
</div>

View File

@ -34,7 +34,13 @@
{% trans "Change User Data" %}
</button>
</div>
<hr>
<div class="text-center">
<a class="btn choice-btn btn-block" href="{% url 'index' %}" role="button">Back to Index</a>
</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,29 @@
{% extends "base_short.html" %}
{% load i18n staticfiles bootstrap3 %}
{% block title %}
<title> Verify your email. </title>
{% endblock %}
{% block content %}
<div class="auth-container">
<div class="auth-bg"></div>
<div class="auth-center">
<div class="auth-content">
<div class="auth-box">
<h1 class="section-heading allcaps">{% trans " Check your email " %}</h1>
<p class="text-center">{% trans "In order to complete the sign up process, please check your email and follow the activation instructions." %}</p>
<form action="{% url 'index' %}" method="get" class="form" novalidated>
<div class="text-center">
<button type="submit" class="btn choice-btn btn-block">
{% trans "Back to indexpage" %}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -27,6 +27,7 @@
</div>
<hr>
<div class="text-center">
<a class="btn choice-btn btn-block" href="{% url 'index' %}" role="button">Back to Index</a><br>
<button type="submit" class="btn choicered-btn btn-block">
{% trans "Delete Account" %}
</button>

View File

@ -0,0 +1,37 @@
{% extends "base_short.html" %}
{% load i18n staticfiles bootstrap3 %}
{% block title %}
<title>Options for {{user}}</title>
{% endblock %}
{% block content %}
<div class="auth-container">
<div class="auth-bg"></div>
<div class="auth-center">
<div class="auth-content">
<div class="auth-box">
<h1 class="section-heading allcaps">{% trans "Seeds of," %} {{user}}</h1><br><br>
<table class="table table-hover text-center">
<tbody>
{% for i in seed %}
<tr>
<td>{{ i.realm }}</td>
<td>{{ i.seed }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<br>
<hr>
<form class="form" novalidate>
<div class="text-center">
<a class="btn choice-btn btn-block" href="{% url 'index' %}" role="button">Back to Index</a><br>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -16,6 +16,7 @@
<form class="form">
<a class="btn choice-btn btn-block" href="{% url 'change_data' %}" role="button">{% trans "Change your userdata" %}</a><br>
<a class="btn choice-btn btn-block" href="{% url 'change_password' %}" role="button">Change your password</a><br>
<a class="btn choice-btn btn-block" href="{% url 'user_seeds' %}" role="button">Show seeds</a><br>
<a class="btn choicered-btn btn-block" href="{% url 'logout' %}" role="button">{% trans "Logout" %}</a><br><br>
</form>
<br>

View File

@ -91,7 +91,7 @@ class LdapManager:
logger.debug("{uid} does not exist. Using it".format(uid=uidNumber))
self._set_max_uid(uidNumber)
try:
uid = user.encode("utf-8")
uid = user # user.encode("utf-8")
conn.add("uid={uid},{customer_dn}".format(
uid=uid, customer_dn=settings.LDAP_CUSTOMER_DN
),

View File

@ -13,19 +13,25 @@ from .views import (
Index,
LogOut,
ResetRequest,
UserCreateAPI
UserCreateAPI,
ActivateAccount,
Seeds,
SeedRetrieveCreate
)
urlpatterns = [
path('register/', Register.as_view(), name="register"),
path('create/', UserCreateAPI.as_view(), name="create"),
path('changedata/', ChangeData.as_view(), name="change_data"),
path('seeds/', Seeds.as_view(), name="user_seeds"),
path('resetpassword/', ResetPassword.as_view(), name="reset_password"),
path('changepassword/', ChangePassword.as_view(), name="change_password"),
path('deleteaccount/', DeleteAccount.as_view(), name="account_delete"),
path('index/', Index.as_view(), name="index"),
path('logout/', LogOut.as_view(), name="logout"),
path('reset/<str:user>/<str:token>/', ResetRequest.as_view()),
path('activate/<str:user>/<str:pwd>/<str:firstname>/<str:lastname>/<str:email>/<str:token>/', ActivateAccount.as_view()),
path('reset/', ResetRequest.as_view(), name="reset"),
path('otp/', SeedRetrieveCreate.as_view(), name="seed"),
path('', Index.as_view(), name="login_index"),
]

View File

@ -14,6 +14,8 @@ from rest_framework.response import Response
from .models import ResetToken
from .forms import LoginForm
from .ungleich_ldap import LdapManager
from decouple import config, Csv
from pyotp import TOTP
import logging
import re
@ -27,6 +29,8 @@ from datetime import datetime
from random import choice, randint
import string
import requests
import json
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
@ -35,6 +39,54 @@ from django.contrib.auth.mixins import LoginRequiredMixin
def is_username_valid(username):
return re.fullmatch(r"^[a-z|0-9|\-|_]+$", username)
admin_seed = config('ADMIN_SEED')
admin_name = config('ADMIN_NAME')
admin_realm = config('ADMIN_REALM')
user_realm = config('USER_REALM')
otp_url = config('OTPSERVER')
def activate_account_link(base_url, user, pwd, firstname, lastname, email, epochutc):
tokengen = PasswordResetTokenGenerator()
pseudouser = PseudoUser()
token = tokengen.make_token(pseudouser)
buser = bytes(user, 'utf-8')
bpwd = bytes(pwd, 'utf-8')
bfirstname = bytes(firstname, 'utf-8')
blasttname = bytes(lastname, 'utf-8')
bemail = bytes(email, 'utf-8')
userpart = b64encode(buser)
pwdpart = b64encode(bpwd)
fnpart = b64encode(bfirstname)
lnpart = b64encode(blasttname)
mailpart = b64encode(bemail)
# create entry into the database
newdbentry = ResetToken(user=user, token=token, creation=epochutc)
newdbentry.save()
# set up the link
link = "{base_url}/activate/{user}/{pwd}/{fn}/{ln}/{mail}/{token}/".format(
base_url=base_url, user=userpart.decode('utf-8'),
pwd=pwdpart.decode('utf-8'),
fn=fnpart.decode('utf-8'),
ln=lnpart.decode('utf-8'),
mail=mailpart.decode('utf-8'),
token=token
)
return link
def clean_db():
"""Revoves outdated tokens"""
# 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
class Index(FormView):
template_name = "landing.html"
form_class = LoginForm
@ -105,34 +157,30 @@ class Register(View):
pwd = r'%s' % password1
try:
ldap_manager = LdapManager()
ldap_manager.create_user(
username, pwd, firstname, lastname, email
creationtime = int(datetime.utcnow().timestamp())
base_url = "{0}://{1}".format(self.request.scheme,
self.request.get_host())
link = activate_account_link(base_url, username, pwd, firstname, lastname, email, creationtime)
email_from = settings.EMAIL_FROM_ADDRESS
to = ['%s <%s>' % (username, email)]
subject = 'Activate your ungleich account'.format(firstname)
body = 'You can activate your ungleich account account by clicking <a href="{link}">here</a>.' \
' You can also copy and paste the following link into the address bar of your browser and follow' \
' the link in order to activate your account.\n\n{link}'.format(link=link)
# Build the email
mail = EmailMessage(
subject=subject,
body=body,
from_email=email_from,
to=to
)
mail.send()
except Exception as e:
return render(request, 'error.html', { 'urlname': urlname,
'service': service,
'error': e } )
# Finally, we send the send user credentials via email
creationtime = int(datetime.utcnow().timestamp())
# Construct the data for the email
email_from = settings.EMAIL_FROM_ADDRESS
to = ['%s <%s>' % (username, email)]
subject = '{}, Welcome to datacenterlight'.format(firstname)
body = 'The username {} was successfully created.\n'.format(username)
# Build the email
mail = EmailMessage(
subject=subject,
body=body,
from_email=email_from,
to=to
)
try:
mail.send()
except Exception as e:
print(e)
pass
return render(request, 'usercreated.html', { 'user': username } )
return render(request, 'confirm_email.html')
class ChangeData(LoginRequiredMixin, View):
login_url = reverse_lazy('login_index')
@ -310,7 +358,7 @@ class ResetRequest(View):
# 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()
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:
@ -349,10 +397,10 @@ class ResetRequest(View):
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.' } )
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
return render(request, 'error.html', {'service': service,
'error': 'The password is too short, please use a longer one. At least 8 characters.'})
ldap_manager = LdapManager()
result = ldap_manager.change_password(
@ -366,17 +414,7 @@ class ResetRequest(View):
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
@ -501,6 +539,58 @@ class PseudoUser():
pk = ''.join(choice(string.ascii_letters + string.digits) for _ in range(20))
password = ''.join(choice(string.ascii_letters + string.digits) for _ in range(30))
class ActivateAccount(View):
def get(self, request, user=None, pwd=None, firstname=None, lastname=None, email=None, token=None):
clean_db()
if token is None:
return HttpResponse('Invalid URL', status=404)
elem_list = [user, pwd, firstname, lastname, email]
clean_list = []
for value in elem_list:
try:
value_temp = bytes(value, 'utf-8')
value_decode = b64decode(value_temp)
value_clean = value_decode.decode('utf-8')
clean_list.append(value_clean)
except Exception as e:
return HttpResponse('Invalid URL', status=404)
checks_out = False
dbentries = ResetToken.objects.all().filter(user=clean_list[0])
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, create user
try:
ldap_manager = LdapManager()
ldap_manager.create_user(
clean_list[0], clean_list[1], clean_list[2], clean_list[3], clean_list[4]
)
req = requests.post(otp_url, data=json.dumps(
{
'auth_token': TOTP(admin_seed).now(),
'auth_name': admin_name,
'auth_realm': admin_realm,
'name': clean_list[0],
'realm': user_realm
}), headers={'Content-Type': 'application/json'})
if req.status_code != 201:
logger.error("User {} failed to create its otp seed".format(clean_list[0]))
#Send welcome email
except Exception as e:
return render(request, 'error.html', {'urlname': 'register',
'service': 'register an user',
'error': e})
return render(request, 'usercreated.html', { 'user': clean_list[0] } )
class UserCreateAPI(APIView):
def post(self, request):
@ -524,25 +614,24 @@ class UserCreateAPI(APIView):
pwd = r'%s' % User.objects.make_random_password()
try:
ldap_manager = LdapManager()
ldap_manager.create_user(
username, pwd, firstname, lastname, email
)
except Exception as e:
return Response('While trying to create the user, an error was encountered: %s' % e, 400)
# send user credentials via email
base_url = "{0}://{1}".format(self.request.scheme,
self.request.get_host())
creationtime = int(datetime.utcnow().timestamp())
link = activate_account_link(base_url, username, pwd, firstname, lastname, email, creationtime)
# Construct the data for the email
email_from = settings.EMAIL_FROM_ADDRESS
to = ['%s <%s>' % (username, email)]
subject = 'Your datacenterlight credentials'
body = 'Your user was successfully created.\n'
subject = 'Ungleich account creation.'
body = 'A request has been sent to our servers to register you as a ungleich user.\n'
body += 'In order to complete the registration process you must ' \
'click <a href="{link}">here</a> or copy & paste the following link into the address bar of ' \
'your browser.\n{link}\n'.format(link=link)
body += 'Your credentials are:\n'
body += 'Username: %s\n\n' % username
body += 'Password: %s\n\n' % pwd
body += 'We strongly recommend you to after log in change your password.\n'
body += 'We strongly recommend after the activation to log in and change your password.\n'
body += 'This link will remain active for 24 hours.\n'
# Build the email
mail = EmailMessage(
subject=subject,
@ -552,6 +641,71 @@ class UserCreateAPI(APIView):
)
try:
mail.send()
except:
return Response('User was created, but failed to send the email', 201)
return Response('User successfully created', 200)
except Exception as e:
return Response('Failed to send the email, please try again', 400)
return Response('An email with activation link has been sent in order to complete your registration. Please check your inbox.', 200)
class SeedRetrieveCreate(APIView):
def post(self, request):
try:
username = request.data['username']
password = request.data[r'password']
realm = request.data['realm']
print(password)
except KeyError:
return Response('You need to specify username, password, and realm values', 400)
# authenticate the user against ldap
user = authenticate(username=username, password=password)
if user is not None:
req = requests.get(otp_url, data=json.dumps(
{
'auth_token': TOTP(admin_seed).now(),
'auth_name': admin_name,
'auth_realm': admin_realm}), headers={'Content-Type': 'application/json'})
response_data = json.loads(req.text)
for elem in response_data:
if elem['name'] == username and elem['realm'] == realm:
return Response(elem, 200)
# If doesn't find a match then check if the realm is allowed and create the user
allowed_realms = config('ALLOWED_REALMS', cast=Csv())
if realm not in allowed_realms:
return Response('Not allowed to perform this action.', 403)
else:
req = requests.post(otp_url, data=json.dumps(
{
'auth_token': TOTP(admin_seed).now(),
'auth_name': admin_name,
'auth_realm': admin_realm,
'name': username,
'realm': realm
}), headers={'Content-Type': 'application/json'})
if req.status_code == 201:
msg = json.loads(req.text)
return Response(msg, 201)
else:
return Response(json.loads(req.text), req.status_code)
else:
return Response('Invalid Credentials', 400)
class Seeds(LoginRequiredMixin, View):
login_url = reverse_lazy('login_index')
def get(self, request):
seedlist = []
response = requests.get(
otp_url,
headers={'Content-Type': 'application/json'},
data=json.dumps(
{'auth_name': admin_name, 'auth_realm': admin_realm, 'auth_token': TOTP(admin_seed).now()}))
response_data = json.loads(response.text)
for i in range(len(response_data)):
if response_data[i]['name'] == request.user.username:
value = {'realm': response_data[i]['realm'], 'seed': response_data[i]['seed']}
seedlist.append(value)
return render(request, 'seed_list.html', {'seed': seedlist})

View File

@ -1,2 +0,0 @@
# Debian/Devuanp
apt install libldap2-dev libsasl2-dev

View File

@ -1,8 +0,0 @@
django < 3
django-auth-ldap
python-ldap
django-bootstrap3
django-filter==2.1.0
python-decouple
ldap3
djangorestframework

22
ungleichuser/manage.py Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ungleichuser.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,14 @@
django==4.0
ldap3
django-crispy-forms
bootstrap4
#django-auth-ldap
# To check
# django-bootstrap3
# django-filter==2.1.0
# python-decouple
# djangorestframework
# requests

View File

@ -0,0 +1,19 @@
body {
margin: 0;
padding: 0;
background-color: #17a2b8;
height: 100vh;
}
#login .container #login-row #login-column #login-box {
margin-top: 120px;
max-width: 600px;
height: 320px;
border: 1px solid #9C9C9C;
background-color: #EAEAEA;
}
#login .container #login-row #login-column #login-box #login-form {
padding: 20px;
}
#login .container #login-row #login-column #login-box #login-form #register-link {
margin-top: -85px;
}

View File

@ -0,0 +1,3 @@
{% block content %}
Your content here
{% endblock %}

View File

@ -0,0 +1,36 @@
<link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<!------ Include the above in your HEAD tag ---------->
<body>
<div id="login">
<h3 class="text-center text-white pt-5">Login form</h3>
<div class="container">
<div id="login-row" class="row justify-content-center align-items-center">
<div id="login-column" class="col-md-6">
<div id="login-box" class="col-md-12">
<form id="login-form" class="form" action="" method="post">
<h3 class="text-center text-info">Login</h3>
<div class="form-group">
<label for="username" class="text-info">Username:</label><br>
<input type="text" name="username" id="username" class="form-control">
</div>
<div class="form-group">
<label for="password" class="text-info">Password:</label><br>
<input type="text" name="password" id="password" class="form-control">
</div>
<div class="form-group">
<label for="remember-me" class="text-info"><span>Remember me</span> <span><input id="remember-me" name="remember-me" type="checkbox"></span></label><br>
<input type="submit" name="submit" class="btn btn-info btn-md" value="submit">
</div>
<div id="register-link" class="text-right">
<a href="#" class="text-info">Register here</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</body>

View File

@ -0,0 +1,42 @@
{% extends "base.html" %}
{% block content %}
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
{% if next %}
{% if user.is_authenticated %}
<p>Your account doesn't have access to this page. To proceed,
please login with an account that has access.</p>
{% else %}
<p>Please login to see this page.</p>
{% endif %}
{% endif %}
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<table>
<tr>
<td>{{ form.username.label_tag }}</td>
<td>{{ form.username }}</td>
</tr>
<tr>
<td>{{ form.password.label_tag }}</td>
<td>{{ form.password }}</td>
</tr>
<tr>
<td>{{ form.random.label_tag }}</td>
<td>{{ form.random }}</td>
</tr>
</table>
<input type="submit" value="login">
<input type="hidden" name="next" value="{{ next }}">
</form>
{# Assumes you setup the password_reset view in your URLconf #}
<p><a href="{% url 'password_reset' %}">Lost password?</a></p>
{% endblock %}

View File

@ -0,0 +1,16 @@
"""
ASGI config for ungleichuser project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/dev/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ungleichuser.settings')
application = get_asgi_application()

View File

@ -0,0 +1,22 @@
import django.contrib.auth.forms as forms
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
# class UngleichAuthenticationForm(forms.AuthenticationForm):
# address = forms.CharField(max_length=45)
class NewUserForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = ("username", "email", "password1", "password2")
def save(self, commit=True):
user = super(NewUserForm, self).save(commit=False)
user.email = self.cleaned_data['email']
if commit:
user.save()
return user

View File

@ -0,0 +1,128 @@
"""
Django settings for ungleichuser project.
Generated by 'django-admin startproject' using Django 4.0rc1.
For more information on this file, see
https://docs.djangoproject.com/en/dev/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/dev/ref/settings/
"""
import os
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-(9@_07zxslciu-%wnc7f$qx%=8esu!fuv(72m!6r4f(&#u8$9s'
# 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',
'crispy_forms',
'ungleichuser'
]
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',
]
ROOT_URLCONF = 'ungleichuser.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'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 = 'ungleichuser.wsgi.application'
# Database
# https://docs.djangoproject.com/en/dev/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/dev/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/dev/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/dev/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/dev/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
CRISPY_TEMPLATE_PACK = 'bootstrap4'

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Title</title>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>

View File

@ -0,0 +1,18 @@
{% extends "ungleichuser/header.html" %}
{% block content %}
{% load crispy_forms_tags %}
<!--Register-->
<div class="container py-5">
<h1>Register</h1>
<form method="POST">
{% csrf_token %}
{{ register_form|crispy }}
<button class="btn btn-primary" type="submit">Register</button>
</form>
<p class="text-center">If you already have an account, <a href="/login">login</a> instead.</p>
</div>
{% endblock %}

View File

@ -0,0 +1,26 @@
"""ungleichuser URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/dev/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
import django.contrib.auth
from django.urls import path, include
from . import views
urlpatterns = [
# path('admin/', admin.site.urls),
# path("", views.homepage, name="homepage"),
path("register", views.register_request, name="register"),
path('accounts/', include('django.contrib.auth.urls')),
]

View File

@ -0,0 +1,9 @@
import ipaddress
import random
v4_addr = ipaddress.IPv4Address(random.randint(0, 2**32))
v6_addr = ipaddress.IPv6Address(random.randint(0, 2**128))
if __name__ == '__main__':
addr_type = random.choice(["ipv6", "ipv4"])
if addr_type == "ipv6":

View File

@ -0,0 +1,21 @@
from django.shortcuts import render, redirect
from .forms import NewUserForm
from django.contrib.auth import login
from django.contrib import messages
def register_request(request):
if request.method == "POST":
form = NewUserForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
messages.success(request, "Registration successful." )
return redirect("main:homepage")
messages.error(request, "Unsuccessful registration. Invalid information.")
form = NewUserForm()
return render (request=request,
template_name="ungleichuser/register.html",
context={"register_form":form})

View File

@ -0,0 +1,16 @@
"""
WSGI config for ungleichuser project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ungleichuser.settings')
application = get_wsgi_application()