Browse Source

Import.

master
crudo 4 years ago
commit
ced4a29aed
6 changed files with 358 additions and 0 deletions
  1. +94
    -0
      .gitignore
  2. +95
    -0
      app.py
  3. +43
    -0
      index.tpl
  4. +3
    -0
      requirements.txt
  5. +12
    -0
      settings.ini
  6. +111
    -0
      static/style.css

+ 94
- 0
.gitignore View 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
- 0
app.py View 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
- 0
index.tpl View 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
- 0
requirements.txt View File

@@ -0,0 +1,3 @@
bottle>=0.12.0
configparser
python-ldap>=2.4.0

+ 12
- 0
settings.ini View 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
- 0
static/style.css View 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…
Cancel
Save