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