commit ced4a29aedfbb1a6de67aef99414e9b05ddbff6b Author: crudo Date: Tue Mar 14 00:36:22 2017 +0100 Import. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62c1e73 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/app.py b/app.py new file mode 100755 index 0000000..b4fa732 --- /dev/null +++ b/app.py @@ -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/', 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() diff --git a/index.tpl b/index.tpl new file mode 100644 index 0000000..1af0906 --- /dev/null +++ b/index.tpl @@ -0,0 +1,43 @@ + + + + + + + + + {{ page_title }} + + + + + +
+

{{ page_title }}

+ +
+ + + + + + + + + + + + + +
+ +
+ %for type, text in get('alerts', []): +
{{ text }}
+ %end +
+
+ + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..49beef0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +bottle>=0.12.0 +configparser +python-ldap>=2.4.0 diff --git a/settings.ini b/settings.ini new file mode 100644 index 0000000..2973da2 --- /dev/null +++ b/settings.ini @@ -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 diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..1064a20 --- /dev/null +++ b/static/style.css @@ -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; + } +}