forked from unit/ldap-panel
Import.
This commit is contained in:
commit
ced4a29aed
94
.gitignore
vendored
Normal file
94
.gitignore
vendored
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*,cover
|
||||||
|
.hypothesis/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# dotenv
|
||||||
|
.env
|
||||||
|
|
||||||
|
# virtualenv
|
||||||
|
.venv
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
95
app.py
Executable file
95
app.py
Executable file
|
@ -0,0 +1,95 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
from os import path
|
||||||
|
|
||||||
|
from configparser import ConfigParser
|
||||||
|
|
||||||
|
import bottle
|
||||||
|
from bottle import get, post, static_file, request, route, template
|
||||||
|
from bottle import SimpleTemplate
|
||||||
|
|
||||||
|
import ldap
|
||||||
|
|
||||||
|
|
||||||
|
@get('/')
|
||||||
|
def get_index():
|
||||||
|
return index_tpl()
|
||||||
|
|
||||||
|
|
||||||
|
@post('/')
|
||||||
|
def post_index():
|
||||||
|
form = request.forms.getunicode
|
||||||
|
|
||||||
|
def error(msg):
|
||||||
|
return index_tpl(username=form('username'), alerts=[('error', msg)])
|
||||||
|
|
||||||
|
if form('new-password') != form('confirm-password'):
|
||||||
|
return error("Password doesn't match the confirmation!")
|
||||||
|
|
||||||
|
if len(form('new-password')) < 8:
|
||||||
|
return error("Password must be at least 8 characters long!")
|
||||||
|
|
||||||
|
try:
|
||||||
|
ldap_change_password(form('username'),
|
||||||
|
form('old-password'), form('new-password'))
|
||||||
|
except Error as e:
|
||||||
|
print("Unsuccessful attemp to change password for {}: {}"
|
||||||
|
.format(form('username'), str(e)))
|
||||||
|
return error(str(e))
|
||||||
|
|
||||||
|
print("Password successfully changed for: {}"
|
||||||
|
.format(form('username')))
|
||||||
|
|
||||||
|
return index_tpl(alerts=[('success', "Password has been changed")])
|
||||||
|
|
||||||
|
|
||||||
|
@route('/static/<filename>', name='static')
|
||||||
|
def serve_static(filename):
|
||||||
|
return static_file(filename, root=path.join(BASE_DIR, 'static'))
|
||||||
|
|
||||||
|
|
||||||
|
def index_tpl(**kwargs):
|
||||||
|
return template('index', **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def ldap_change_password(username, old, new):
|
||||||
|
dn_name = "uid={},{}".format(username, CONF['ldap']['base'])
|
||||||
|
l = ldap.initialize(CONF['ldap']['host'])
|
||||||
|
l.set_option(ldap.OPT_X_TLS_CACERTFILE, CONF['ldap']['tls_cacert'])
|
||||||
|
l.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_DEMAND)
|
||||||
|
l.start_tls_s()
|
||||||
|
l.simple_bind_s(dn_name, old)
|
||||||
|
l.passwd_s(dn_name, old, new)
|
||||||
|
l.unbind_s()
|
||||||
|
|
||||||
|
|
||||||
|
def read_config():
|
||||||
|
config = ConfigParser()
|
||||||
|
config.read([path.join(BASE_DIR, 'settings.ini'),
|
||||||
|
os.getenv('CONF_FILE', '')])
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
class Error(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
BASE_DIR = path.dirname(__file__)
|
||||||
|
CONF = read_config()
|
||||||
|
|
||||||
|
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
||||||
|
|
||||||
|
bottle.TEMPLATE_PATH = [BASE_DIR]
|
||||||
|
|
||||||
|
# Set default attributes to pass into templates.
|
||||||
|
SimpleTemplate.defaults = dict(CONF['html'])
|
||||||
|
SimpleTemplate.defaults['url'] = bottle.url
|
||||||
|
|
||||||
|
|
||||||
|
# Run bottle internal server when invoked directly (mainly for development).
|
||||||
|
if __name__ == '__main__':
|
||||||
|
bottle.run(**CONF['server'])
|
||||||
|
# Run bottle in application mode (in production under uWSGI server).
|
||||||
|
else:
|
||||||
|
application = bottle.default_app()
|
43
index.tpl
Normal file
43
index.tpl
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
|
||||||
|
<title>{{ page_title }}</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{{ url('static', filename='style.css') }}">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>{{ page_title }}</h1>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input id="username" name="username" value="{{ get('username', '') }}" type="text" required autofocus>
|
||||||
|
|
||||||
|
<label for="old-password">Old password</label>
|
||||||
|
<input id="old-password" name="old-password" type="password" required>
|
||||||
|
|
||||||
|
<label for="new-password">New password</label>
|
||||||
|
<input id="new-password" name="new-password" type="password"
|
||||||
|
pattern=".{8,}" x-moz-errormessage="Password must be at least 8 characters long." required>
|
||||||
|
|
||||||
|
<label for="confirm-password">Confirm new password</label>
|
||||||
|
<input id="confirm-password" name="confirm-password" type="password"
|
||||||
|
pattern=".{8,}" x-moz-errormessage="Password must be at least 8 characters long." required>
|
||||||
|
|
||||||
|
<button type="submit">Update password</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="alerts">
|
||||||
|
%for type, text in get('alerts', []):
|
||||||
|
<div class="alert {{ type }}">{{ text }}</div>
|
||||||
|
%end
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
bottle>=0.12.0
|
||||||
|
configparser
|
||||||
|
python-ldap>=2.4.0
|
12
settings.ini
Normal file
12
settings.ini
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[html]
|
||||||
|
page_title = Change your password on unit.macaomilano.org
|
||||||
|
|
||||||
|
[ldap]
|
||||||
|
host = ldap://unit.macaomilano.org:389
|
||||||
|
base = ou=Hackers,dc=unit,dc=macaomilano,dc=org
|
||||||
|
tls_cacert = /etc/ssl/cert.pem
|
||||||
|
|
||||||
|
[server]
|
||||||
|
server = auto
|
||||||
|
host = localhost
|
||||||
|
port = 8080
|
111
static/style.css
Normal file
111
static/style.css
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
/* TODO make it cooler! */
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
margin-bottom: 2.5em;
|
||||||
|
margin-top: 2em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 16rem;
|
||||||
|
padding: 2rem 2.5rem 1.5rem 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
background-color: #FAFAFA;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
box-shadow: inset 0 1px 3px #DDD;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 0.4em 0.6em;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
background-color: #FFF;
|
||||||
|
border-color: #51A7E8;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) inset, 0 0 5px rgba(81, 167, 232, 0.5);
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: #666;
|
||||||
|
display: block;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 1em 0 0.25em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #60B044;
|
||||||
|
background-image: linear-gradient(#8ADD6D, #60B044);
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
border: 1px solid #5CA941;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 2em 0 0.5em 0;
|
||||||
|
padding: 0.5em 0.7em;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.3);
|
||||||
|
user-select: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus,
|
||||||
|
button:hover {
|
||||||
|
background-color: #569E3D;
|
||||||
|
background-image: linear-gradient(#79D858, #569E3D);
|
||||||
|
border-color: #4A993E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alerts {
|
||||||
|
margin: 2rem auto 0 auto;
|
||||||
|
max-width: 30rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
border: 1px solid;
|
||||||
|
color: #fff;
|
||||||
|
padding: 0.7em 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert.error {
|
||||||
|
background-color: #E74C3C;
|
||||||
|
border-color: #C0392B;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert.success {
|
||||||
|
background-color: #60B044;
|
||||||
|
border-color: #5CA941;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media only screen and (max-width: 480px) {
|
||||||
|
|
||||||
|
form {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user